- errs.map((e) => (
-
+ Object.entries(errors).map(([field, errs]: [string, string[]], fieldIndex) =>
+ errs.map((e, index) => (
+
{field}:`: ${errs}`
))
diff --git a/x-pack/examples/alerting_example/server/alert_types/always_firing.ts b/x-pack/examples/alerting_example/server/alert_types/always_firing.ts
index bb1cb0d97689b..d02406a23045e 100644
--- a/x-pack/examples/alerting_example/server/alert_types/always_firing.ts
+++ b/x-pack/examples/alerting_example/server/alert_types/always_firing.ts
@@ -5,25 +5,31 @@
*/
import uuid from 'uuid';
-import { range } from 'lodash';
+import { range, random } from 'lodash';
import { AlertType } from '../../../../plugins/alerts/server';
import { DEFAULT_INSTANCES_TO_GENERATE, ALERTING_EXAMPLE_APP_ID } from '../../common/constants';
+const ACTION_GROUPS = [
+ { id: 'small', name: 'small' },
+ { id: 'medium', name: 'medium' },
+ { id: 'large', name: 'large' },
+];
+
export const alertType: AlertType = {
id: 'example.always-firing',
name: 'Always firing',
- actionGroups: [{ id: 'default', name: 'default' }],
- defaultActionGroupId: 'default',
+ actionGroups: ACTION_GROUPS,
+ defaultActionGroupId: 'small',
async executor({ services, params: { instances = DEFAULT_INSTANCES_TO_GENERATE }, state }) {
const count = (state.count ?? 0) + 1;
range(instances)
- .map(() => ({ id: uuid.v4() }))
- .forEach((instance: { id: string }) => {
+ .map(() => ({ id: uuid.v4(), tshirtSize: ACTION_GROUPS[random(0, 2)].id! }))
+ .forEach((instance: { id: string; tshirtSize: string }) => {
services
.alertInstanceFactory(instance.id)
.replaceState({ triggerdOnCycle: count })
- .scheduleActions('default');
+ .scheduleActions(instance.tshirtSize);
});
return {
diff --git a/x-pack/plugins/actions/README.md b/x-pack/plugins/actions/README.md
index 4fef9bc582d08..432a4bfff7a6b 100644
--- a/x-pack/plugins/actions/README.md
+++ b/x-pack/plugins/actions/README.md
@@ -69,18 +69,21 @@ Table of Contents
- [`secrets`](#secrets-6)
- [`params`](#params-6)
- [`subActionParams (pushToService)`](#subactionparams-pushtoservice)
+ - [`subActionParams (getFields)`](#subactionparams-getfields-1)
- [Jira](#jira)
- [`config`](#config-7)
- [`secrets`](#secrets-7)
- [`params`](#params-7)
- [`subActionParams (pushToService)`](#subactionparams-pushtoservice-1)
- [`subActionParams (issueTypes)`](#subactionparams-issuetypes)
+ - [`subActionParams (getFields)`](#subactionparams-getfields-2)
- [`subActionParams (pushToService)`](#subactionparams-pushtoservice-2)
- [IBM Resilient](#ibm-resilient)
- [`config`](#config-8)
- [`secrets`](#secrets-8)
- [`params`](#params-8)
- [`subActionParams (pushToService)`](#subactionparams-pushtoservice-3)
+ - [`subActionParams (getFields)`](#subactionparams-getfields-3)
- [Command Line Utility](#command-line-utility)
- [Developing New Action Types](#developing-new-action-types)
- [licensing](#licensing)
@@ -563,7 +566,7 @@ The ServiceNow action uses the [V2 Table API](https://developer.servicenow.com/a
| Property | Description | Type |
| --------------- | ------------------------------------------------------------------------------------ | ------ |
-| subAction | The sub action to perform. It can be `pushToService`, `handshake`, and `getIncident` | string |
+| subAction | The sub action to perform. It can be `getFields`, `pushToService`, `handshake`, and `getIncident` | string |
| subActionParams | The parameters of the sub action | object |
#### `subActionParams (pushToService)`
@@ -580,6 +583,10 @@ The ServiceNow action uses the [V2 Table API](https://developer.servicenow.com/a
| urgency | The name of the urgency in ServiceNow. | string _(optional)_ |
| impact | The name of the impact in ServiceNow. | string _(optional)_ |
+#### `subActionParams (getFields)`
+
+No parameters for `getFields` sub-action. Provide an empty object `{}`.
+
---
## Jira
@@ -606,7 +613,7 @@ The Jira action uses the [V2 API](https://developer.atlassian.com/cloud/jira/pla
| Property | Description | Type |
| --------------- | ----------------------------------------------------------------------------------------------------------------------- | ------ |
-| subAction | The sub action to perform. It can be `pushToService`, `handshake`, `getIncident`, `issueTypes`, and `fieldsByIssueType` | string |
+| subAction | The sub action to perform. It can be `getFields`, `pushToService`, `handshake`, `getIncident`, `issueTypes`, and `fieldsByIssueType` | string |
| subActionParams | The parameters of the sub action | object |
#### `subActionParams (pushToService)`
@@ -627,6 +634,10 @@ The Jira action uses the [V2 API](https://developer.atlassian.com/cloud/jira/pla
No parameters for `issueTypes` sub-action. Provide an empty object `{}`.
+#### `subActionParams (getFields)`
+
+No parameters for `getFields` sub-action. Provide an empty object `{}`.
+
#### `subActionParams (pushToService)`
| Property | Description | Type |
@@ -655,7 +666,7 @@ ID: `.resilient`
| Property | Description | Type |
| --------------- | ------------------------------------------------------------------------------------ | ------ |
-| subAction | The sub action to perform. It can be `pushToService`, `handshake`, and `getIncident` | string |
+| subAction | The sub action to perform. It can be `getFields`, `pushToService`, `handshake`, and `getIncident` | string |
| subActionParams | The parameters of the sub action | object |
#### `subActionParams (pushToService)`
@@ -670,6 +681,10 @@ ID: `.resilient`
| incidentTypes | An array with the ids of IBM Resilient incident types. | number[] _(optional)_ |
| severityCode | IBM Resilient id of the severity code. | number _(optional)_ |
+#### `subActionParams (getFields)`
+
+No parameters for `getFields` sub-action. Provide an empty object `{}`.
+
# Command Line Utility
The [`kbn-action`](https://github.com/pmuellr/kbn-action) tool can be used to send HTTP requests to the Actions plugin. For instance, to create a Slack action from the `.slack` Action Type, use the following command:
diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/api.test.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/api.test.ts
index e8fa9f76df778..5a7617ada1bf0 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/jira/api.test.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/jira/api.test.ts
@@ -15,804 +15,792 @@ describe('api', () => {
beforeEach(() => {
externalService = externalServiceMock.create();
- jest.clearAllMocks();
});
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- describe('pushToService', () => {
- describe('create incident - cases', () => {
- test('it creates an incident', async () => {
- const params = { ...apiParams, externalId: null };
- const res = await api.pushToService({
- externalService,
- mapping,
- params,
- logger: mockedLogger,
- });
-
- expect(res).toEqual({
- id: 'incident-1',
- title: 'CK-1',
- pushedDate: '2020-04-27T10:59:46.202Z',
- url: 'https://siem-kibana.atlassian.net/browse/CK-1',
- comments: [
- {
- commentId: 'case-comment-1',
- pushedDate: '2020-04-27T10:59:46.202Z',
- },
- {
- commentId: 'case-comment-2',
- pushedDate: '2020-04-27T10:59:46.202Z',
- },
- ],
- });
- });
-
- test('it creates an incident without comments', async () => {
- const params = { ...apiParams, externalId: null, comments: [] };
- const res = await api.pushToService({
- externalService,
- mapping,
- params,
- logger: mockedLogger,
- });
-
- expect(res).toEqual({
- id: 'incident-1',
- title: 'CK-1',
- pushedDate: '2020-04-27T10:59:46.202Z',
- url: 'https://siem-kibana.atlassian.net/browse/CK-1',
- });
- });
-
- test('it calls createIncident correctly', async () => {
- const params = { ...apiParams, externalId: null };
- await api.pushToService({ externalService, mapping, params, logger: mockedLogger });
-
- expect(externalService.createIncident).toHaveBeenCalledWith({
- incident: {
- labels: ['kibana', 'elastic'],
- priority: 'High',
- issueType: '10006',
- parent: null,
- description:
- 'Incident description (created at 2020-04-27T10:59:46.202Z by Elastic User)',
- summary: 'Incident title (created at 2020-04-27T10:59:46.202Z by Elastic User)',
- },
- });
- expect(externalService.updateIncident).not.toHaveBeenCalled();
- });
-
- test('it calls createIncident correctly without mapping', async () => {
- const params = { ...apiParams, externalId: null };
- await api.pushToService({ externalService, mapping: null, params, logger: mockedLogger });
-
- expect(externalService.createIncident).toHaveBeenCalledWith({
- incident: {
- description: 'Incident description',
- summary: 'Incident title',
- issueType: '10006',
- labels: ['kibana', 'elastic'],
- priority: 'High',
- parent: null,
- },
- });
- expect(externalService.updateIncident).not.toHaveBeenCalled();
+ describe('create incident - cases', () => {
+ test('it creates an incident', async () => {
+ const params = { ...apiParams, externalId: null };
+ const res = await api.pushToService({
+ externalService,
+ mapping,
+ params,
+ logger: mockedLogger,
});
- test('it calls createComment correctly', async () => {
- const params = { ...apiParams, externalId: null };
- await api.pushToService({ externalService, mapping, params, logger: mockedLogger });
- expect(externalService.createComment).toHaveBeenCalledTimes(2);
- expect(externalService.createComment).toHaveBeenNthCalledWith(1, {
- incidentId: 'incident-1',
- comment: {
+ expect(res).toEqual({
+ id: 'incident-1',
+ title: 'CK-1',
+ pushedDate: '2020-04-27T10:59:46.202Z',
+ url: 'https://siem-kibana.atlassian.net/browse/CK-1',
+ comments: [
+ {
commentId: 'case-comment-1',
- comment: 'A comment (added at 2020-04-27T10:59:46.202Z by Elastic User)',
- createdAt: '2020-04-27T10:59:46.202Z',
- createdBy: {
- fullName: 'Elastic User',
- username: 'elastic',
- },
- updatedAt: '2020-04-27T10:59:46.202Z',
- updatedBy: {
- fullName: 'Elastic User',
- username: 'elastic',
- },
+ pushedDate: '2020-04-27T10:59:46.202Z',
},
- });
-
- expect(externalService.createComment).toHaveBeenNthCalledWith(2, {
- incidentId: 'incident-1',
- comment: {
+ {
commentId: 'case-comment-2',
- comment: 'Another comment (added at 2020-04-27T10:59:46.202Z by Elastic User)',
- createdAt: '2020-04-27T10:59:46.202Z',
- createdBy: {
- fullName: 'Elastic User',
- username: 'elastic',
- },
- updatedAt: '2020-04-27T10:59:46.202Z',
- updatedBy: {
- fullName: 'Elastic User',
- username: 'elastic',
- },
+ pushedDate: '2020-04-27T10:59:46.202Z',
},
- });
+ ],
});
+ });
- test('it calls createComment correctly without mapping', async () => {
- const params = { ...apiParams, externalId: null };
- await api.pushToService({ externalService, mapping: null, params, logger: mockedLogger });
- expect(externalService.createComment).toHaveBeenCalledTimes(2);
- expect(externalService.createComment).toHaveBeenNthCalledWith(1, {
- incidentId: 'incident-1',
- comment: {
- commentId: 'case-comment-1',
- comment: 'A comment',
- createdAt: '2020-04-27T10:59:46.202Z',
- createdBy: {
- fullName: 'Elastic User',
- username: 'elastic',
- },
- updatedAt: '2020-04-27T10:59:46.202Z',
- updatedBy: {
- fullName: 'Elastic User',
- username: 'elastic',
- },
- },
- });
+ test('it creates an incident without comments', async () => {
+ const params = { ...apiParams, externalId: null, comments: [] };
+ const res = await api.pushToService({
+ externalService,
+ mapping,
+ params,
+ logger: mockedLogger,
+ });
- expect(externalService.createComment).toHaveBeenNthCalledWith(2, {
- incidentId: 'incident-1',
- comment: {
- commentId: 'case-comment-2',
- comment: 'Another comment',
- createdAt: '2020-04-27T10:59:46.202Z',
- createdBy: {
- fullName: 'Elastic User',
- username: 'elastic',
- },
- updatedAt: '2020-04-27T10:59:46.202Z',
- updatedBy: {
- fullName: 'Elastic User',
- username: 'elastic',
- },
- },
- });
+ expect(res).toEqual({
+ id: 'incident-1',
+ title: 'CK-1',
+ pushedDate: '2020-04-27T10:59:46.202Z',
+ url: 'https://siem-kibana.atlassian.net/browse/CK-1',
});
});
- describe('update incident', () => {
- test('it updates an incident', async () => {
- const res = await api.pushToService({
- externalService,
- mapping,
- params: apiParams,
- logger: mockedLogger,
- });
-
- expect(res).toEqual({
- id: 'incident-1',
- title: 'CK-1',
- pushedDate: '2020-04-27T10:59:46.202Z',
- url: 'https://siem-kibana.atlassian.net/browse/CK-1',
- comments: [
- {
- commentId: 'case-comment-1',
- pushedDate: '2020-04-27T10:59:46.202Z',
- },
- {
- commentId: 'case-comment-2',
- pushedDate: '2020-04-27T10:59:46.202Z',
- },
- ],
- });
- });
-
- test('it updates an incident without comments', async () => {
- const params = { ...apiParams, comments: [] };
- const res = await api.pushToService({
- externalService,
- mapping,
- params,
- logger: mockedLogger,
- });
-
- expect(res).toEqual({
- id: 'incident-1',
- title: 'CK-1',
- pushedDate: '2020-04-27T10:59:46.202Z',
- url: 'https://siem-kibana.atlassian.net/browse/CK-1',
- });
- });
-
- test('it calls updateIncident correctly', async () => {
- const params = { ...apiParams };
- await api.pushToService({ externalService, mapping, params, logger: mockedLogger });
-
- expect(externalService.updateIncident).toHaveBeenCalledWith({
- incidentId: 'incident-3',
- incident: {
- labels: ['kibana', 'elastic'],
- priority: 'High',
- issueType: '10006',
- parent: null,
- description:
- 'Incident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
- summary: 'Incident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
+ test('it calls createIncident correctly', async () => {
+ const params = { ...apiParams, externalId: null };
+ await api.pushToService({ externalService, mapping, params, logger: mockedLogger });
+
+ expect(externalService.createIncident).toHaveBeenCalledWith({
+ incident: {
+ labels: ['kibana', 'elastic'],
+ priority: 'High',
+ issueType: '10006',
+ parent: null,
+ description: 'Incident description (created at 2020-04-27T10:59:46.202Z by Elastic User)',
+ summary: 'Incident title (created at 2020-04-27T10:59:46.202Z by Elastic User)',
+ },
+ });
+ expect(externalService.updateIncident).not.toHaveBeenCalled();
+ });
+
+ test('it calls createIncident correctly without mapping', async () => {
+ const params = { ...apiParams, externalId: null };
+ await api.pushToService({ externalService, mapping: null, params, logger: mockedLogger });
+
+ expect(externalService.createIncident).toHaveBeenCalledWith({
+ incident: {
+ description: 'Incident description',
+ summary: 'Incident title',
+ issueType: '10006',
+ labels: ['kibana', 'elastic'],
+ priority: 'High',
+ parent: null,
+ },
+ });
+ expect(externalService.updateIncident).not.toHaveBeenCalled();
+ });
+
+ test('it calls createComment correctly', async () => {
+ const params = { ...apiParams, externalId: null };
+ await api.pushToService({ externalService, mapping, params, logger: mockedLogger });
+ expect(externalService.createComment).toHaveBeenCalledTimes(2);
+ expect(externalService.createComment).toHaveBeenNthCalledWith(1, {
+ incidentId: 'incident-1',
+ comment: {
+ commentId: 'case-comment-1',
+ comment: 'A comment (added at 2020-04-27T10:59:46.202Z by Elastic User)',
+ createdAt: '2020-04-27T10:59:46.202Z',
+ createdBy: {
+ fullName: 'Elastic User',
+ username: 'elastic',
},
- });
- expect(externalService.createIncident).not.toHaveBeenCalled();
- });
-
- test('it calls updateIncident correctly without mapping', async () => {
- const params = { ...apiParams };
- await api.pushToService({ externalService, mapping: null, params, logger: mockedLogger });
-
- expect(externalService.updateIncident).toHaveBeenCalledWith({
- incidentId: 'incident-3',
- incident: {
- description: 'Incident description',
- summary: 'Incident title',
- issueType: '10006',
- labels: ['kibana', 'elastic'],
- priority: 'High',
- parent: null,
+ updatedAt: '2020-04-27T10:59:46.202Z',
+ updatedBy: {
+ fullName: 'Elastic User',
+ username: 'elastic',
},
- });
- expect(externalService.createIncident).not.toHaveBeenCalled();
+ },
});
- test('it calls createComment correctly', async () => {
- const params = { ...apiParams };
- await api.pushToService({ externalService, mapping, params, logger: mockedLogger });
- expect(externalService.createComment).toHaveBeenCalledTimes(2);
- expect(externalService.createComment).toHaveBeenNthCalledWith(1, {
- incidentId: 'incident-1',
- comment: {
- commentId: 'case-comment-1',
- comment: 'A comment (added at 2020-04-27T10:59:46.202Z by Elastic User)',
- createdAt: '2020-04-27T10:59:46.202Z',
- createdBy: {
- fullName: 'Elastic User',
- username: 'elastic',
- },
- updatedAt: '2020-04-27T10:59:46.202Z',
- updatedBy: {
- fullName: 'Elastic User',
- username: 'elastic',
- },
+ expect(externalService.createComment).toHaveBeenNthCalledWith(2, {
+ incidentId: 'incident-1',
+ comment: {
+ commentId: 'case-comment-2',
+ comment: 'Another comment (added at 2020-04-27T10:59:46.202Z by Elastic User)',
+ createdAt: '2020-04-27T10:59:46.202Z',
+ createdBy: {
+ fullName: 'Elastic User',
+ username: 'elastic',
},
- });
-
- expect(externalService.createComment).toHaveBeenNthCalledWith(2, {
- incidentId: 'incident-1',
- comment: {
- commentId: 'case-comment-2',
- comment: 'Another comment (added at 2020-04-27T10:59:46.202Z by Elastic User)',
- createdAt: '2020-04-27T10:59:46.202Z',
- createdBy: {
- fullName: 'Elastic User',
- username: 'elastic',
- },
- updatedAt: '2020-04-27T10:59:46.202Z',
- updatedBy: {
- fullName: 'Elastic User',
- username: 'elastic',
- },
+ updatedAt: '2020-04-27T10:59:46.202Z',
+ updatedBy: {
+ fullName: 'Elastic User',
+ username: 'elastic',
},
- });
+ },
});
+ });
- test('it calls createComment correctly without mapping', async () => {
- const params = { ...apiParams };
- await api.pushToService({ externalService, mapping: null, params, logger: mockedLogger });
- expect(externalService.createComment).toHaveBeenCalledTimes(2);
- expect(externalService.createComment).toHaveBeenNthCalledWith(1, {
- incidentId: 'incident-1',
- comment: {
- commentId: 'case-comment-1',
- comment: 'A comment',
- createdAt: '2020-04-27T10:59:46.202Z',
- createdBy: {
- fullName: 'Elastic User',
- username: 'elastic',
- },
- updatedAt: '2020-04-27T10:59:46.202Z',
- updatedBy: {
- fullName: 'Elastic User',
- username: 'elastic',
- },
+ test('it calls createComment correctly without mapping', async () => {
+ const params = { ...apiParams, externalId: null };
+ await api.pushToService({ externalService, mapping: null, params, logger: mockedLogger });
+ expect(externalService.createComment).toHaveBeenCalledTimes(2);
+ expect(externalService.createComment).toHaveBeenNthCalledWith(1, {
+ incidentId: 'incident-1',
+ comment: {
+ commentId: 'case-comment-1',
+ comment: 'A comment',
+ createdAt: '2020-04-27T10:59:46.202Z',
+ createdBy: {
+ fullName: 'Elastic User',
+ username: 'elastic',
+ },
+ updatedAt: '2020-04-27T10:59:46.202Z',
+ updatedBy: {
+ fullName: 'Elastic User',
+ username: 'elastic',
},
- });
+ },
+ });
- expect(externalService.createComment).toHaveBeenNthCalledWith(2, {
- incidentId: 'incident-1',
- comment: {
- commentId: 'case-comment-2',
- comment: 'Another comment',
- createdAt: '2020-04-27T10:59:46.202Z',
- createdBy: {
- fullName: 'Elastic User',
- username: 'elastic',
- },
- updatedAt: '2020-04-27T10:59:46.202Z',
- updatedBy: {
- fullName: 'Elastic User',
- username: 'elastic',
- },
+ expect(externalService.createComment).toHaveBeenNthCalledWith(2, {
+ incidentId: 'incident-1',
+ comment: {
+ commentId: 'case-comment-2',
+ comment: 'Another comment',
+ createdAt: '2020-04-27T10:59:46.202Z',
+ createdBy: {
+ fullName: 'Elastic User',
+ username: 'elastic',
+ },
+ updatedAt: '2020-04-27T10:59:46.202Z',
+ updatedBy: {
+ fullName: 'Elastic User',
+ username: 'elastic',
},
- });
+ },
});
});
+ });
- describe('issueTypes', () => {
- test('it returns the issue types correctly', async () => {
- const res = await api.issueTypes({
- externalService,
- params: {},
- });
- expect(res).toEqual([
+ describe('update incident', () => {
+ test('it updates an incident', async () => {
+ const res = await api.pushToService({
+ externalService,
+ mapping,
+ params: apiParams,
+ logger: mockedLogger,
+ });
+
+ expect(res).toEqual({
+ id: 'incident-1',
+ title: 'CK-1',
+ pushedDate: '2020-04-27T10:59:46.202Z',
+ url: 'https://siem-kibana.atlassian.net/browse/CK-1',
+ comments: [
{
- id: '10006',
- name: 'Task',
+ commentId: 'case-comment-1',
+ pushedDate: '2020-04-27T10:59:46.202Z',
},
{
- id: '10007',
- name: 'Bug',
+ commentId: 'case-comment-2',
+ pushedDate: '2020-04-27T10:59:46.202Z',
},
- ]);
+ ],
});
});
- describe('fieldsByIssueType', () => {
- test('it returns the fields correctly', async () => {
- const res = await api.fieldsByIssueType({
- externalService,
- params: { id: '10006' },
- });
- expect(res).toEqual({
- summary: { allowedValues: [], defaultValue: {} },
- priority: {
- allowedValues: [
- {
- name: 'Medium',
- id: '3',
- },
- ],
- defaultValue: { name: 'Medium', id: '3' },
- },
- });
+ test('it updates an incident without comments', async () => {
+ const params = { ...apiParams, comments: [] };
+ const res = await api.pushToService({
+ externalService,
+ mapping,
+ params,
+ logger: mockedLogger,
+ });
+
+ expect(res).toEqual({
+ id: 'incident-1',
+ title: 'CK-1',
+ pushedDate: '2020-04-27T10:59:46.202Z',
+ url: 'https://siem-kibana.atlassian.net/browse/CK-1',
});
});
- describe('getIssues', () => {
- test('it returns the issues correctly', async () => {
- const res = await api.issues({
- externalService,
- params: { title: 'Title test' },
- });
- expect(res).toEqual([
- {
- id: '10267',
- key: 'RJ-107',
- title: 'Test title',
- },
- ]);
+ test('it calls updateIncident correctly', async () => {
+ const params = { ...apiParams };
+ await api.pushToService({ externalService, mapping, params, logger: mockedLogger });
+
+ expect(externalService.updateIncident).toHaveBeenCalledWith({
+ incidentId: 'incident-3',
+ incident: {
+ labels: ['kibana', 'elastic'],
+ priority: 'High',
+ issueType: '10006',
+ parent: null,
+ description: 'Incident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
+ summary: 'Incident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
+ },
});
+ expect(externalService.createIncident).not.toHaveBeenCalled();
});
- describe('getIssue', () => {
- test('it returns the issue correctly', async () => {
- const res = await api.issue({
- externalService,
- params: { id: 'RJ-107' },
- });
- expect(res).toEqual({
- id: '10267',
- key: 'RJ-107',
- title: 'Test title',
- });
+ test('it calls updateIncident correctly without mapping', async () => {
+ const params = { ...apiParams };
+ await api.pushToService({ externalService, mapping: null, params, logger: mockedLogger });
+
+ expect(externalService.updateIncident).toHaveBeenCalledWith({
+ incidentId: 'incident-3',
+ incident: {
+ description: 'Incident description',
+ summary: 'Incident title',
+ issueType: '10006',
+ labels: ['kibana', 'elastic'],
+ priority: 'High',
+ parent: null,
+ },
});
+ expect(externalService.createIncident).not.toHaveBeenCalled();
});
- describe('mapping variations', () => {
- test('overwrite & append', async () => {
- mapping.set('title', {
- target: 'summary',
- actionType: 'overwrite',
- });
-
- mapping.set('description', {
- target: 'description',
- actionType: 'append',
- });
-
- mapping.set('comments', {
- target: 'comments',
- actionType: 'append',
- });
-
- mapping.set('summary', {
- target: 'title',
- actionType: 'overwrite',
- });
-
- await api.pushToService({
- externalService,
- mapping,
- params: apiParams,
- logger: mockedLogger,
- });
- expect(externalService.updateIncident).toHaveBeenCalledWith({
- incidentId: 'incident-3',
- incident: {
- labels: ['kibana', 'elastic'],
- priority: 'High',
- issueType: '10006',
- parent: null,
- summary: 'Incident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
- description:
- 'description from jira \r\nIncident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
- },
- });
- });
-
- test('nothing & append', async () => {
- mapping.set('title', {
- target: 'summary',
- actionType: 'nothing',
- });
-
- mapping.set('description', {
- target: 'description',
- actionType: 'append',
- });
-
- mapping.set('comments', {
- target: 'comments',
- actionType: 'append',
- });
-
- mapping.set('summary', {
- target: 'title',
- actionType: 'nothing',
- });
-
- await api.pushToService({
- externalService,
- mapping,
- params: apiParams,
- logger: mockedLogger,
- });
- expect(externalService.updateIncident).toHaveBeenCalledWith({
- incidentId: 'incident-3',
- incident: {
- labels: ['kibana', 'elastic'],
- priority: 'High',
- issueType: '10006',
- parent: null,
- description:
- 'description from jira \r\nIncident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
+ test('it calls createComment correctly', async () => {
+ const params = { ...apiParams };
+ await api.pushToService({ externalService, mapping, params, logger: mockedLogger });
+ expect(externalService.createComment).toHaveBeenCalledTimes(2);
+ expect(externalService.createComment).toHaveBeenNthCalledWith(1, {
+ incidentId: 'incident-1',
+ comment: {
+ commentId: 'case-comment-1',
+ comment: 'A comment (added at 2020-04-27T10:59:46.202Z by Elastic User)',
+ createdAt: '2020-04-27T10:59:46.202Z',
+ createdBy: {
+ fullName: 'Elastic User',
+ username: 'elastic',
},
- });
- });
-
- test('append & append', async () => {
- mapping.set('title', {
- target: 'summary',
- actionType: 'append',
- });
-
- mapping.set('description', {
- target: 'description',
- actionType: 'append',
- });
-
- mapping.set('comments', {
- target: 'comments',
- actionType: 'append',
- });
-
- mapping.set('summary', {
- target: 'title',
- actionType: 'append',
- });
-
- await api.pushToService({
- externalService,
- mapping,
- params: apiParams,
- logger: mockedLogger,
- });
- expect(externalService.updateIncident).toHaveBeenCalledWith({
- incidentId: 'incident-3',
- incident: {
- labels: ['kibana', 'elastic'],
- priority: 'High',
- issueType: '10006',
- parent: null,
- summary:
- 'title from jira \r\nIncident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
- description:
- 'description from jira \r\nIncident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
+ updatedAt: '2020-04-27T10:59:46.202Z',
+ updatedBy: {
+ fullName: 'Elastic User',
+ username: 'elastic',
},
- });
- });
-
- test('nothing & nothing', async () => {
- mapping.set('title', {
- target: 'summary',
- actionType: 'nothing',
- });
-
- mapping.set('description', {
- target: 'description',
- actionType: 'nothing',
- });
-
- mapping.set('comments', {
- target: 'comments',
- actionType: 'append',
- });
-
- mapping.set('summary', {
- target: 'title',
- actionType: 'nothing',
- });
-
- await api.pushToService({
- externalService,
- mapping,
- params: apiParams,
- logger: mockedLogger,
- });
- expect(externalService.updateIncident).toHaveBeenCalledWith({
- incidentId: 'incident-3',
- incident: {
- labels: ['kibana', 'elastic'],
- priority: 'High',
- issueType: '10006',
- parent: null,
+ },
+ });
+
+ expect(externalService.createComment).toHaveBeenNthCalledWith(2, {
+ incidentId: 'incident-1',
+ comment: {
+ commentId: 'case-comment-2',
+ comment: 'Another comment (added at 2020-04-27T10:59:46.202Z by Elastic User)',
+ createdAt: '2020-04-27T10:59:46.202Z',
+ createdBy: {
+ fullName: 'Elastic User',
+ username: 'elastic',
},
- });
- });
-
- test('overwrite & nothing', async () => {
- mapping.set('title', {
- target: 'summary',
- actionType: 'overwrite',
- });
-
- mapping.set('description', {
- target: 'description',
- actionType: 'nothing',
- });
-
- mapping.set('comments', {
- target: 'comments',
- actionType: 'append',
- });
-
- mapping.set('summary', {
- target: 'title',
- actionType: 'overwrite',
- });
-
- await api.pushToService({
- externalService,
- mapping,
- params: apiParams,
- logger: mockedLogger,
- });
- expect(externalService.updateIncident).toHaveBeenCalledWith({
- incidentId: 'incident-3',
- incident: {
- labels: ['kibana', 'elastic'],
- priority: 'High',
- issueType: '10006',
- parent: null,
- summary: 'Incident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
+ updatedAt: '2020-04-27T10:59:46.202Z',
+ updatedBy: {
+ fullName: 'Elastic User',
+ username: 'elastic',
},
- });
- });
-
- test('overwrite & overwrite', async () => {
- mapping.set('title', {
- target: 'summary',
- actionType: 'overwrite',
- });
-
- mapping.set('description', {
- target: 'description',
- actionType: 'overwrite',
- });
-
- mapping.set('comments', {
- target: 'comments',
- actionType: 'append',
- });
-
- mapping.set('summary', {
- target: 'title',
- actionType: 'overwrite',
- });
-
- await api.pushToService({
- externalService,
- mapping,
- params: apiParams,
- logger: mockedLogger,
- });
- expect(externalService.updateIncident).toHaveBeenCalledWith({
- incidentId: 'incident-3',
- incident: {
- labels: ['kibana', 'elastic'],
- priority: 'High',
- issueType: '10006',
- parent: null,
- summary: 'Incident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
- description:
- 'Incident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
+ },
+ });
+ });
+
+ test('it calls createComment correctly without mapping', async () => {
+ const params = { ...apiParams };
+ await api.pushToService({ externalService, mapping: null, params, logger: mockedLogger });
+ expect(externalService.createComment).toHaveBeenCalledTimes(2);
+ expect(externalService.createComment).toHaveBeenNthCalledWith(1, {
+ incidentId: 'incident-1',
+ comment: {
+ commentId: 'case-comment-1',
+ comment: 'A comment',
+ createdAt: '2020-04-27T10:59:46.202Z',
+ createdBy: {
+ fullName: 'Elastic User',
+ username: 'elastic',
},
- });
- });
-
- test('nothing & overwrite', async () => {
- mapping.set('title', {
- target: 'summary',
- actionType: 'nothing',
- });
-
- mapping.set('description', {
- target: 'description',
- actionType: 'overwrite',
- });
-
- mapping.set('comments', {
- target: 'comments',
- actionType: 'append',
- });
-
- mapping.set('summary', {
- target: 'title',
- actionType: 'nothing',
- });
-
- await api.pushToService({
- externalService,
- mapping,
- params: apiParams,
- logger: mockedLogger,
- });
- expect(externalService.updateIncident).toHaveBeenCalledWith({
- incidentId: 'incident-3',
- incident: {
- labels: ['kibana', 'elastic'],
- priority: 'High',
- issueType: '10006',
- parent: null,
- description:
- 'Incident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
+ updatedAt: '2020-04-27T10:59:46.202Z',
+ updatedBy: {
+ fullName: 'Elastic User',
+ username: 'elastic',
},
- });
- });
-
- test('append & overwrite', async () => {
- mapping.set('title', {
- target: 'summary',
- actionType: 'append',
- });
-
- mapping.set('description', {
- target: 'description',
- actionType: 'overwrite',
- });
-
- mapping.set('comments', {
- target: 'comments',
- actionType: 'append',
- });
-
- mapping.set('summary', {
- target: 'title',
- actionType: 'append',
- });
-
- await api.pushToService({
- externalService,
- mapping,
- params: apiParams,
- logger: mockedLogger,
- });
- expect(externalService.updateIncident).toHaveBeenCalledWith({
- incidentId: 'incident-3',
- incident: {
- labels: ['kibana', 'elastic'],
- priority: 'High',
- issueType: '10006',
- parent: null,
- summary:
- 'title from jira \r\nIncident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
- description:
- 'Incident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
+ },
+ });
+
+ expect(externalService.createComment).toHaveBeenNthCalledWith(2, {
+ incidentId: 'incident-1',
+ comment: {
+ commentId: 'case-comment-2',
+ comment: 'Another comment',
+ createdAt: '2020-04-27T10:59:46.202Z',
+ createdBy: {
+ fullName: 'Elastic User',
+ username: 'elastic',
},
- });
- });
-
- test('append & nothing', async () => {
- mapping.set('title', {
- target: 'summary',
- actionType: 'append',
- });
-
- mapping.set('description', {
- target: 'description',
- actionType: 'nothing',
- });
-
- mapping.set('comments', {
- target: 'comments',
- actionType: 'append',
- });
-
- mapping.set('summary', {
- target: 'title',
- actionType: 'append',
- });
-
- await api.pushToService({
- externalService,
- mapping,
- params: apiParams,
- logger: mockedLogger,
- });
- expect(externalService.updateIncident).toHaveBeenCalledWith({
- incidentId: 'incident-3',
- incident: {
- labels: ['kibana', 'elastic'],
- priority: 'High',
- issueType: '10006',
- parent: null,
- summary:
- 'title from jira \r\nIncident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
+ updatedAt: '2020-04-27T10:59:46.202Z',
+ updatedBy: {
+ fullName: 'Elastic User',
+ username: 'elastic',
},
- });
- });
-
- test('comment nothing', async () => {
- mapping.set('title', {
- target: 'summary',
- actionType: 'overwrite',
- });
-
- mapping.set('description', {
- target: 'description',
- actionType: 'nothing',
- });
-
- mapping.set('comments', {
- target: 'comments',
- actionType: 'nothing',
- });
-
- mapping.set('summary', {
- target: 'title',
- actionType: 'overwrite',
- });
-
- await api.pushToService({
- externalService,
- mapping,
- params: apiParams,
- logger: mockedLogger,
- });
- expect(externalService.createComment).not.toHaveBeenCalled();
+ },
+ });
+ });
+ });
+
+ describe('issueTypes', () => {
+ test('it returns the issue types correctly', async () => {
+ const res = await api.issueTypes({
+ externalService,
+ params: {},
+ });
+ expect(res).toEqual([
+ {
+ id: '10006',
+ name: 'Task',
+ },
+ {
+ id: '10007',
+ name: 'Bug',
+ },
+ ]);
+ });
+ });
+
+ describe('fieldsByIssueType', () => {
+ test('it returns the fields correctly', async () => {
+ const res = await api.fieldsByIssueType({
+ externalService,
+ params: { id: '10006' },
+ });
+ expect(res).toEqual({
+ summary: { allowedValues: [], defaultValue: {} },
+ priority: {
+ allowedValues: [
+ {
+ name: 'Medium',
+ id: '3',
+ },
+ ],
+ defaultValue: { name: 'Medium', id: '3' },
+ },
+ });
+ });
+ });
+
+ describe('getIssues', () => {
+ test('it returns the issues correctly', async () => {
+ const res = await api.issues({
+ externalService,
+ params: { title: 'Title test' },
+ });
+ expect(res).toEqual([
+ {
+ id: '10267',
+ key: 'RJ-107',
+ title: 'Test title',
+ },
+ ]);
+ });
+ });
+
+ describe('getIssue', () => {
+ test('it returns the issue correctly', async () => {
+ const res = await api.issue({
+ externalService,
+ params: { id: 'RJ-107' },
+ });
+ expect(res).toEqual({
+ id: '10267',
+ key: 'RJ-107',
+ title: 'Test title',
+ });
+ });
+ });
+
+ describe('mapping variations', () => {
+ test('overwrite & append', async () => {
+ mapping.set('title', {
+ target: 'summary',
+ actionType: 'overwrite',
+ });
+
+ mapping.set('description', {
+ target: 'description',
+ actionType: 'append',
+ });
+
+ mapping.set('comments', {
+ target: 'comments',
+ actionType: 'append',
+ });
+
+ mapping.set('summary', {
+ target: 'title',
+ actionType: 'overwrite',
+ });
+
+ await api.pushToService({
+ externalService,
+ mapping,
+ params: apiParams,
+ logger: mockedLogger,
+ });
+ expect(externalService.updateIncident).toHaveBeenCalledWith({
+ incidentId: 'incident-3',
+ incident: {
+ labels: ['kibana', 'elastic'],
+ priority: 'High',
+ issueType: '10006',
+ parent: null,
+ summary: 'Incident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
+ description:
+ 'description from jira \r\nIncident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
+ },
+ });
+ });
+
+ test('nothing & append', async () => {
+ mapping.set('title', {
+ target: 'summary',
+ actionType: 'nothing',
+ });
+
+ mapping.set('description', {
+ target: 'description',
+ actionType: 'append',
+ });
+
+ mapping.set('comments', {
+ target: 'comments',
+ actionType: 'append',
+ });
+
+ mapping.set('summary', {
+ target: 'title',
+ actionType: 'nothing',
+ });
+
+ await api.pushToService({
+ externalService,
+ mapping,
+ params: apiParams,
+ logger: mockedLogger,
+ });
+ expect(externalService.updateIncident).toHaveBeenCalledWith({
+ incidentId: 'incident-3',
+ incident: {
+ labels: ['kibana', 'elastic'],
+ priority: 'High',
+ issueType: '10006',
+ parent: null,
+ description:
+ 'description from jira \r\nIncident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
+ },
+ });
+ });
+
+ test('append & append', async () => {
+ mapping.set('title', {
+ target: 'summary',
+ actionType: 'append',
+ });
+
+ mapping.set('description', {
+ target: 'description',
+ actionType: 'append',
+ });
+
+ mapping.set('comments', {
+ target: 'comments',
+ actionType: 'append',
+ });
+
+ mapping.set('summary', {
+ target: 'title',
+ actionType: 'append',
+ });
+
+ await api.pushToService({
+ externalService,
+ mapping,
+ params: apiParams,
+ logger: mockedLogger,
+ });
+ expect(externalService.updateIncident).toHaveBeenCalledWith({
+ incidentId: 'incident-3',
+ incident: {
+ labels: ['kibana', 'elastic'],
+ priority: 'High',
+ issueType: '10006',
+ parent: null,
+ summary:
+ 'title from jira \r\nIncident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
+ description:
+ 'description from jira \r\nIncident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
+ },
+ });
+ });
+
+ test('nothing & nothing', async () => {
+ mapping.set('title', {
+ target: 'summary',
+ actionType: 'nothing',
+ });
+
+ mapping.set('description', {
+ target: 'description',
+ actionType: 'nothing',
+ });
+
+ mapping.set('comments', {
+ target: 'comments',
+ actionType: 'append',
+ });
+
+ mapping.set('summary', {
+ target: 'title',
+ actionType: 'nothing',
+ });
+
+ await api.pushToService({
+ externalService,
+ mapping,
+ params: apiParams,
+ logger: mockedLogger,
+ });
+ expect(externalService.updateIncident).toHaveBeenCalledWith({
+ incidentId: 'incident-3',
+ incident: {
+ labels: ['kibana', 'elastic'],
+ priority: 'High',
+ issueType: '10006',
+ parent: null,
+ },
+ });
+ });
+
+ test('overwrite & nothing', async () => {
+ mapping.set('title', {
+ target: 'summary',
+ actionType: 'overwrite',
+ });
+
+ mapping.set('description', {
+ target: 'description',
+ actionType: 'nothing',
+ });
+
+ mapping.set('comments', {
+ target: 'comments',
+ actionType: 'append',
+ });
+
+ mapping.set('summary', {
+ target: 'title',
+ actionType: 'overwrite',
+ });
+
+ await api.pushToService({
+ externalService,
+ mapping,
+ params: apiParams,
+ logger: mockedLogger,
+ });
+ expect(externalService.updateIncident).toHaveBeenCalledWith({
+ incidentId: 'incident-3',
+ incident: {
+ labels: ['kibana', 'elastic'],
+ priority: 'High',
+ issueType: '10006',
+ parent: null,
+ summary: 'Incident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
+ },
+ });
+ });
+
+ test('overwrite & overwrite', async () => {
+ mapping.set('title', {
+ target: 'summary',
+ actionType: 'overwrite',
+ });
+
+ mapping.set('description', {
+ target: 'description',
+ actionType: 'overwrite',
+ });
+
+ mapping.set('comments', {
+ target: 'comments',
+ actionType: 'append',
+ });
+
+ mapping.set('summary', {
+ target: 'title',
+ actionType: 'overwrite',
+ });
+
+ await api.pushToService({
+ externalService,
+ mapping,
+ params: apiParams,
+ logger: mockedLogger,
+ });
+ expect(externalService.updateIncident).toHaveBeenCalledWith({
+ incidentId: 'incident-3',
+ incident: {
+ labels: ['kibana', 'elastic'],
+ priority: 'High',
+ issueType: '10006',
+ parent: null,
+ summary: 'Incident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
+ description: 'Incident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
+ },
+ });
+ });
+
+ test('nothing & overwrite', async () => {
+ mapping.set('title', {
+ target: 'summary',
+ actionType: 'nothing',
+ });
+
+ mapping.set('description', {
+ target: 'description',
+ actionType: 'overwrite',
+ });
+
+ mapping.set('comments', {
+ target: 'comments',
+ actionType: 'append',
+ });
+
+ mapping.set('summary', {
+ target: 'title',
+ actionType: 'nothing',
+ });
+
+ await api.pushToService({
+ externalService,
+ mapping,
+ params: apiParams,
+ logger: mockedLogger,
+ });
+ expect(externalService.updateIncident).toHaveBeenCalledWith({
+ incidentId: 'incident-3',
+ incident: {
+ labels: ['kibana', 'elastic'],
+ priority: 'High',
+ issueType: '10006',
+ parent: null,
+ description: 'Incident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
+ },
+ });
+ });
+
+ test('append & overwrite', async () => {
+ mapping.set('title', {
+ target: 'summary',
+ actionType: 'append',
+ });
+
+ mapping.set('description', {
+ target: 'description',
+ actionType: 'overwrite',
+ });
+
+ mapping.set('comments', {
+ target: 'comments',
+ actionType: 'append',
+ });
+
+ mapping.set('summary', {
+ target: 'title',
+ actionType: 'append',
+ });
+
+ await api.pushToService({
+ externalService,
+ mapping,
+ params: apiParams,
+ logger: mockedLogger,
+ });
+ expect(externalService.updateIncident).toHaveBeenCalledWith({
+ incidentId: 'incident-3',
+ incident: {
+ labels: ['kibana', 'elastic'],
+ priority: 'High',
+ issueType: '10006',
+ parent: null,
+ summary:
+ 'title from jira \r\nIncident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
+ description: 'Incident description (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
+ },
+ });
+ });
+
+ test('append & nothing', async () => {
+ mapping.set('title', {
+ target: 'summary',
+ actionType: 'append',
+ });
+
+ mapping.set('description', {
+ target: 'description',
+ actionType: 'nothing',
+ });
+
+ mapping.set('comments', {
+ target: 'comments',
+ actionType: 'append',
+ });
+
+ mapping.set('summary', {
+ target: 'title',
+ actionType: 'append',
+ });
+
+ await api.pushToService({
+ externalService,
+ mapping,
+ params: apiParams,
+ logger: mockedLogger,
+ });
+ expect(externalService.updateIncident).toHaveBeenCalledWith({
+ incidentId: 'incident-3',
+ incident: {
+ labels: ['kibana', 'elastic'],
+ priority: 'High',
+ issueType: '10006',
+ parent: null,
+ summary:
+ 'title from jira \r\nIncident title (updated at 2020-04-27T10:59:46.202Z by Elastic User)',
+ },
+ });
+ });
+
+ test('comment nothing', async () => {
+ mapping.set('title', {
+ target: 'summary',
+ actionType: 'overwrite',
+ });
+
+ mapping.set('description', {
+ target: 'description',
+ actionType: 'nothing',
+ });
+
+ mapping.set('comments', {
+ target: 'comments',
+ actionType: 'nothing',
+ });
+
+ mapping.set('summary', {
+ target: 'title',
+ actionType: 'overwrite',
+ });
+
+ await api.pushToService({
+ externalService,
+ mapping,
+ params: apiParams,
+ logger: mockedLogger,
});
+ expect(externalService.createComment).not.toHaveBeenCalled();
});
});
});
diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/api.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/api.ts
index 679c1541964ce..feeb69b1d1a0e 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/jira/api.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/jira/api.ts
@@ -17,6 +17,7 @@ import {
PushToServiceApiParams,
PushToServiceResponse,
GetIssueHandlerArgs,
+ GetCommonFieldsHandlerArgs,
} from './types';
// TODO: to remove, need to support Case
@@ -39,6 +40,11 @@ const getIssueTypesHandler = async ({ externalService }: GetIssueTypesHandlerArg
return res;
};
+const getFieldsHandler = async ({ externalService }: GetCommonFieldsHandlerArgs) => {
+ const res = await externalService.getFields();
+ return res;
+};
+
const getFieldsByIssueTypeHandler = async ({
externalService,
params,
@@ -157,6 +163,7 @@ const pushToServiceHandler = async ({
};
export const api: ExternalServiceApi = {
+ getFields: getFieldsHandler,
handshake: handshakeHandler,
pushToService: pushToServiceHandler,
getIncident: getIncidentHandler,
diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts
index 9d6ff90c33700..c70c0810926f4 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts
@@ -40,6 +40,7 @@ interface GetActionTypeParams {
}
const supportedSubActions: string[] = [
+ 'getFields',
'pushToService',
'issueTypes',
'fieldsByIssueType',
@@ -145,6 +146,13 @@ async function executor(
});
}
+ if (subAction === 'getFields') {
+ data = await api.getFields({
+ externalService,
+ params: subActionParams,
+ });
+ }
+
if (subAction === 'issues') {
const getIssuesParams = subActionParams as ExecutorSubActionGetIssuesParams;
data = await api.issues({
diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/mocks.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/mocks.ts
index b98eda799e3aa..87a0f156a0c2a 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/jira/mocks.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/jira/mocks.ts
@@ -73,6 +73,20 @@ const createMock = (): jest.Mocked => {
key: 'RJ-107',
title: 'Test title',
})),
+ getFields: jest.fn().mockImplementation(() => ({
+ description: {
+ allowedValues: [],
+ defaultValue: {},
+ required: true,
+ schema: { type: 'string' },
+ },
+ summary: {
+ allowedValues: [],
+ defaultValue: {},
+ required: true,
+ schema: { type: 'string' },
+ },
+ })),
};
service.createComment.mockImplementationOnce(() =>
@@ -97,7 +111,6 @@ const createMock = (): jest.Mocked => {
const externalServiceMock = {
create: createMock,
};
-
const mapping: Map> = new Map();
mapping.set('title', {
diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/schema.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/schema.ts
index 513ca2cf18e6c..70b60ada9c386 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/jira/schema.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/jira/schema.ts
@@ -55,6 +55,7 @@ export const ExecutorSubActionGetIncidentParamsSchema = schema.object({
});
// Reserved for future implementation
+export const ExecutorSubActionCommonFieldsParamsSchema = schema.object({});
export const ExecutorSubActionHandshakeParamsSchema = schema.object({});
export const ExecutorSubActionGetCapabilitiesParamsSchema = schema.object({});
export const ExecutorSubActionGetIssueTypesParamsSchema = schema.object({});
@@ -65,6 +66,10 @@ export const ExecutorSubActionGetIssuesParamsSchema = schema.object({ title: sch
export const ExecutorSubActionGetIssueParamsSchema = schema.object({ id: schema.string() });
export const ExecutorParamsSchema = schema.oneOf([
+ schema.object({
+ subAction: schema.literal('getFields'),
+ subActionParams: ExecutorSubActionCommonFieldsParamsSchema,
+ }),
schema.object({
subAction: schema.literal('getIncident'),
subActionParams: ExecutorSubActionGetIncidentParamsSchema,
diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts
index fe4e135c76fc3..2165ba56428c9 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts
@@ -57,8 +57,10 @@ const fieldsResponse = {
id: '10006',
name: 'Task',
fields: {
- summary: { fieldId: 'summary' },
+ summary: { required: true, schema: { type: 'string' }, fieldId: 'summary' },
priority: {
+ required: false,
+ schema: { type: 'string' },
fieldId: 'priority',
allowedValues: [
{
@@ -198,7 +200,7 @@ describe('Jira service', () => {
error.response = { data: { errors: { summary: 'Required field' } } };
throw error;
});
- expect(service.getIncident('1')).rejects.toThrow(
+ await expect(service.getIncident('1')).rejects.toThrow(
'[Action][Jira]: Unable to get incident with id 1. Error: An error has occurred Reason: Required field'
);
});
@@ -348,7 +350,7 @@ describe('Jira service', () => {
throw error;
});
- expect(
+ await expect(
service.createIncident({
incident: {
summary: 'title',
@@ -442,7 +444,7 @@ describe('Jira service', () => {
throw error;
});
- expect(
+ await expect(
service.updateIncident({
incidentId: '1',
incident: {
@@ -526,7 +528,7 @@ describe('Jira service', () => {
throw error;
});
- expect(
+ await expect(
service.createComment({
incidentId: '1',
comment: {
@@ -587,7 +589,7 @@ describe('Jira service', () => {
throw error;
});
- expect(service.getCapabilities()).rejects.toThrow(
+ await expect(service.getCapabilities()).rejects.toThrow(
'[Action][Jira]: Unable to get capabilities. Error: An error has occurred. Reason: Could not get capabilities'
);
});
@@ -657,7 +659,7 @@ describe('Jira service', () => {
throw error;
});
- expect(service.getIssueTypes()).rejects.toThrow(
+ await expect(service.getIssueTypes()).rejects.toThrow(
'[Action][Jira]: Unable to get issue types. Error: An error has occurred. Reason: Could not get issue types'
);
});
@@ -741,7 +743,7 @@ describe('Jira service', () => {
throw error;
});
- expect(service.getIssueTypes()).rejects.toThrow(
+ await expect(service.getIssueTypes()).rejects.toThrow(
'[Action][Jira]: Unable to get issue types. Error: An error has occurred. Reason: Could not get issue types'
);
});
@@ -765,6 +767,8 @@ describe('Jira service', () => {
expect(res).toEqual({
priority: {
+ required: false,
+ schema: { type: 'string' },
allowedValues: [
{ id: '1', name: 'Highest' },
{ id: '2', name: 'High' },
@@ -774,7 +778,12 @@ describe('Jira service', () => {
],
defaultValue: { id: '3', name: 'Medium' },
},
- summary: { allowedValues: [], defaultValue: {} },
+ summary: {
+ required: true,
+ schema: { type: 'string' },
+ allowedValues: [],
+ defaultValue: {},
+ },
});
});
@@ -815,7 +824,7 @@ describe('Jira service', () => {
throw error;
});
- expect(service.getFieldsByIssueType('10006')).rejects.toThrow(
+ await expect(service.getFieldsByIssueType('10006')).rejects.toThrow(
'[Action][Jira]: Unable to get fields. Error: An error has occurred. Reason: Could not get fields'
);
});
@@ -837,8 +846,10 @@ describe('Jira service', () => {
requestMock.mockImplementationOnce(() => ({
data: {
values: [
- { fieldId: 'summary' },
+ { required: true, schema: { type: 'string' }, fieldId: 'summary' },
{
+ required: false,
+ schema: { type: 'string' },
fieldId: 'priority',
allowedValues: [
{
@@ -859,10 +870,17 @@ describe('Jira service', () => {
expect(res).toEqual({
priority: {
+ required: false,
+ schema: { type: 'string' },
allowedValues: [{ id: '3', name: 'Medium' }],
defaultValue: { id: '3', name: 'Medium' },
},
- summary: { allowedValues: [], defaultValue: {} },
+ summary: {
+ required: true,
+ schema: { type: 'string' },
+ allowedValues: [],
+ defaultValue: {},
+ },
});
});
@@ -881,8 +899,10 @@ describe('Jira service', () => {
requestMock.mockImplementationOnce(() => ({
data: {
values: [
- { fieldId: 'summary' },
+ { required: true, schema: { type: 'string' }, fieldId: 'summary' },
{
+ required: true,
+ schema: { type: 'string' },
fieldId: 'priority',
allowedValues: [
{
@@ -927,7 +947,7 @@ describe('Jira service', () => {
throw error;
});
- expect(service.getFieldsByIssueType('10006')).rejects.toThrow(
+ await expect(service.getFieldsByIssueType('10006')).rejects.toThrowError(
'[Action][Jira]: Unable to get fields. Error: An error has occurred. Reason: Could not get issue types'
);
});
@@ -976,7 +996,7 @@ describe('Jira service', () => {
throw error;
});
- expect(service.getIssues('Test title')).rejects.toThrow(
+ await expect(service.getIssues('Test title')).rejects.toThrow(
'[Action][Jira]: Unable to get issues. Error: An error has occurred. Reason: Could not get issue types'
);
});
@@ -1020,9 +1040,128 @@ describe('Jira service', () => {
throw error;
});
- expect(service.getIssue('RJ-107')).rejects.toThrow(
+ await expect(service.getIssue('RJ-107')).rejects.toThrow(
'[Action][Jira]: Unable to get issue with id RJ-107. Error: An error has occurred. Reason: Could not get issue types'
);
});
});
+
+ describe('getFields', () => {
+ const callMocks = () => {
+ requestMock
+ .mockImplementationOnce(() => ({
+ data: {
+ capabilities: {
+ 'list-project-issuetypes':
+ 'https://siem-kibana.atlassian.net/rest/capabilities/list-project-issuetypes',
+ 'list-issuetype-fields':
+ 'https://siem-kibana.atlassian.net/rest/capabilities/list-issuetype-fields',
+ },
+ },
+ }))
+ .mockImplementationOnce(() => ({
+ data: {
+ values: issueTypesResponse.data.projects[0].issuetypes,
+ },
+ }))
+ .mockImplementationOnce(() => ({
+ data: {
+ capabilities: {
+ 'list-project-issuetypes':
+ 'https://siem-kibana.atlassian.net/rest/capabilities/list-project-issuetypes',
+ 'list-issuetype-fields':
+ 'https://siem-kibana.atlassian.net/rest/capabilities/list-issuetype-fields',
+ },
+ },
+ }))
+ .mockImplementationOnce(() => ({
+ data: {
+ capabilities: {
+ 'list-project-issuetypes':
+ 'https://siem-kibana.atlassian.net/rest/capabilities/list-project-issuetypes',
+ 'list-issuetype-fields':
+ 'https://siem-kibana.atlassian.net/rest/capabilities/list-issuetype-fields',
+ },
+ },
+ }))
+ .mockImplementationOnce(() => ({
+ data: {
+ values: [
+ { required: true, schema: { type: 'string' }, fieldId: 'summary' },
+ { required: true, schema: { type: 'string' }, fieldId: 'description' },
+ {
+ required: false,
+ schema: { type: 'string' },
+ fieldId: 'priority',
+ allowedValues: [
+ {
+ name: 'Medium',
+ id: '3',
+ },
+ ],
+ defaultValue: {
+ name: 'Medium',
+ id: '3',
+ },
+ },
+ ],
+ },
+ }))
+ .mockImplementationOnce(() => ({
+ data: {
+ values: [
+ { required: true, schema: { type: 'string' }, fieldId: 'summary' },
+ { required: true, schema: { type: 'string' }, fieldId: 'description' },
+ ],
+ },
+ }));
+ };
+ beforeEach(() => {
+ jest.resetAllMocks();
+ });
+ test('it should call request with correct arguments', async () => {
+ callMocks();
+ await service.getFields();
+ const callUrls = [
+ 'https://siem-kibana.atlassian.net/rest/capabilities',
+ 'https://siem-kibana.atlassian.net/rest/api/2/issue/createmeta/CK/issuetypes',
+ 'https://siem-kibana.atlassian.net/rest/capabilities',
+ 'https://siem-kibana.atlassian.net/rest/capabilities',
+ 'https://siem-kibana.atlassian.net/rest/api/2/issue/createmeta/CK/issuetypes/10006',
+ 'https://siem-kibana.atlassian.net/rest/api/2/issue/createmeta/CK/issuetypes/10007',
+ ];
+ requestMock.mock.calls.forEach((call, i) => {
+ expect(call[0].url).toEqual(callUrls[i]);
+ });
+ });
+ test('it returns common fields correctly', async () => {
+ callMocks();
+ const res = await service.getFields();
+ expect(res).toEqual({
+ description: {
+ allowedValues: [],
+ defaultValue: {},
+ required: true,
+ schema: { type: 'string' },
+ },
+ summary: {
+ allowedValues: [],
+ defaultValue: {},
+ required: true,
+ schema: { type: 'string' },
+ },
+ });
+ });
+
+ test('it should throw an error', async () => {
+ requestMock.mockImplementation(() => {
+ const error: ResponseError = new Error('An error has occurred');
+ error.response = { data: { errors: { summary: 'Required field' } } };
+ throw error;
+ });
+ await expect(service.getFields()).rejects.toThrow(
+ '[Action][Jira]: Unable to get capabilities. Error: An error has occurred. Reason: Required field'
+ );
+ });
+ });
});
diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts
index f5347891f4f70..b3c5bb4a84de5 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts
@@ -8,18 +8,20 @@ import axios from 'axios';
import { Logger } from '../../../../../../src/core/server';
import {
- ExternalServiceCredentials,
- ExternalService,
+ CreateCommentParams,
CreateIncidentParams,
- UpdateIncidentParams,
- JiraPublicConfigurationType,
- JiraSecretConfigurationType,
+ ExternalService,
+ ExternalServiceCommentResponse,
+ ExternalServiceCredentials,
+ ExternalServiceIncidentResponse,
Fields,
- CreateCommentParams,
+ FieldSchema,
+ GetCommonFieldsResponse,
Incident,
+ JiraPublicConfigurationType,
+ JiraSecretConfigurationType,
ResponseError,
- ExternalServiceCommentResponse,
- ExternalServiceIncidentResponse,
+ UpdateIncidentParams,
} from './types';
import * as i18n from './translations';
@@ -127,14 +129,21 @@ export const createExternalService = (
issueTypes.map((type) => ({ id: type.id, name: type.name }));
const normalizeFields = (fields: {
- [key: string]: { allowedValues?: Array<{}>; defaultValue?: {} };
+ [key: string]: {
+ allowedValues?: Array<{}>;
+ defaultValue?: {};
+ required: boolean;
+ schema: FieldSchema;
+ };
}) =>
Object.keys(fields ?? {}).reduce((fieldsAcc, fieldKey) => {
return {
...fieldsAcc,
[fieldKey]: {
+ required: fields[fieldKey]?.required,
allowedValues: fields[fieldKey]?.allowedValues ?? [],
defaultValue: fields[fieldKey]?.defaultValue ?? {},
+ schema: fields[fieldKey]?.schema,
},
};
}, {});
@@ -326,7 +335,6 @@ export const createExternalService = (
const getIssueTypes = async () => {
const capabilitiesResponse = await getCapabilities();
const supportsNewAPI = hasSupportForNewAPI(capabilitiesResponse);
-
try {
if (!supportsNewAPI) {
const res = await request({
@@ -366,7 +374,6 @@ export const createExternalService = (
const getFieldsByIssueType = async (issueTypeId: string) => {
const capabilitiesResponse = await getCapabilities();
const supportsNewAPI = hasSupportForNewAPI(capabilitiesResponse);
-
try {
if (!supportsNewAPI) {
const res = await request({
@@ -378,6 +385,7 @@ export const createExternalService = (
});
const fields = res.data.projects[0]?.issuetypes[0]?.fields || {};
+
return normalizeFields(fields);
} else {
const res = await request({
@@ -409,6 +417,30 @@ export const createExternalService = (
}
};
+ const getFields = async () => {
+ try {
+ const issueTypes = await getIssueTypes();
+ const fieldsPerIssueType = await Promise.all(
+ issueTypes.map((issueType) => getFieldsByIssueType(issueType.id))
+ );
+ return fieldsPerIssueType.reduce((acc: GetCommonFieldsResponse, fieldTypesByIssue) => {
+ const currentListOfFields = Object.keys(acc);
+ return currentListOfFields.length === 0
+ ? fieldTypesByIssue
+ : currentListOfFields.reduce(
+ (add: GetCommonFieldsResponse, field) =>
+ Object.keys(fieldTypesByIssue).includes(field)
+ ? { ...add, [field]: acc[field] }
+ : add,
+ {}
+ );
+ }, {});
+ } catch (error) {
+ // errors that happen here would be thrown in the contained async calls
+ throw error;
+ }
+ };
+
const getIssues = async (title: string) => {
const query = `${searchUrl}?jql=${encodeURIComponent(
`project="${projectKey}" and summary ~"${title}"`
@@ -461,6 +493,7 @@ export const createExternalService = (
};
return {
+ getFields,
getIncident,
createIncident,
updateIncident,
diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/types.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/types.ts
index 7d650a22fba1b..e142637010a98 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/jira/types.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/jira/types.ts
@@ -79,11 +79,34 @@ export interface CreateCommentParams {
comment: Comment;
}
+export interface FieldsSchema {
+ type: string;
+ [key: string]: string;
+}
+
+export interface ExternalServiceFields {
+ clauseNames: string[];
+ custom: boolean;
+ id: string;
+ key: string;
+ name: string;
+ navigatable: boolean;
+ orderable: boolean;
+ schema: FieldsSchema;
+ searchable: boolean;
+}
+
export type GetIssueTypesResponse = Array<{ id: string; name: string }>;
+
+export interface FieldSchema {
+ type: string;
+ items?: string;
+}
export type GetFieldsByIssueTypeResponse = Record<
string,
- { allowedValues: Array<{}>; defaultValue: {} }
+ { allowedValues: Array<{}>; defaultValue: {}; required: boolean; schema: FieldSchema }
>;
+export type GetCommonFieldsResponse = GetFieldsByIssueTypeResponse;
export type GetIssuesResponse = Array<{ id: string; key: string; title: string }>;
export interface GetIssueResponse {
@@ -93,15 +116,16 @@ export interface GetIssueResponse {
}
export interface ExternalService {
- getIncident: (id: string) => Promise;
- createIncident: (params: CreateIncidentParams) => Promise;
- updateIncident: (params: UpdateIncidentParams) => Promise;
createComment: (params: CreateCommentParams) => Promise;
+ createIncident: (params: CreateIncidentParams) => Promise;
+ getFields: () => Promise;
getCapabilities: () => Promise;
- getIssueTypes: () => Promise;
getFieldsByIssueType: (issueTypeId: string) => Promise;
- getIssues: (title: string) => Promise;
+ getIncident: (id: string) => Promise;
getIssue: (id: string) => Promise;
+ getIssues: (title: string) => Promise;
+ getIssueTypes: () => Promise;
+ updateIncident: (params: UpdateIncidentParams) => Promise;
}
export interface PushToServiceApiParams extends ExecutorSubActionPushParams {
@@ -157,6 +181,11 @@ export interface GetIssueTypesHandlerArgs {
params: ExecutorSubActionGetIssueTypesParams;
}
+export interface GetCommonFieldsHandlerArgs {
+ externalService: ExternalService;
+ params: ExecutorSubActionGetIssueTypesParams;
+}
+
export interface GetFieldsByIssueTypeHandlerArgs {
externalService: ExternalService;
params: ExecutorSubActionGetFieldsByIssueTypeParams;
@@ -177,15 +206,16 @@ export interface GetIssueHandlerArgs {
}
export interface ExternalServiceApi {
- handshake: (args: HandshakeApiHandlerArgs) => Promise;
- pushToService: (args: PushToServiceApiHandlerArgs) => Promise;
+ getFields: (args: GetCommonFieldsHandlerArgs) => Promise;
getIncident: (args: GetIncidentApiHandlerArgs) => Promise;
+ handshake: (args: HandshakeApiHandlerArgs) => Promise;
issueTypes: (args: GetIssueTypesHandlerArgs) => Promise;
+ pushToService: (args: PushToServiceApiHandlerArgs) => Promise;
fieldsByIssueType: (
args: GetFieldsByIssueTypeHandlerArgs
) => Promise;
- issues: (args: GetIssuesHandlerArgs) => Promise;
issue: (args: GetIssueHandlerArgs) => Promise;
+ issues: (args: GetIssuesHandlerArgs) => Promise;
}
export type JiraExecutorResultData =
diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/api.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/api.ts
index 46d9c114297a9..29f2594d2b6f8 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/resilient/api.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/api.ts
@@ -15,6 +15,7 @@ import {
GetSeverityHandlerArgs,
PushToServiceApiParams,
PushToServiceResponse,
+ GetCommonFieldsHandlerArgs,
} from './types';
// TODO: to remove, need to support Case
@@ -32,6 +33,10 @@ const getIncidentHandler = async ({
params,
}: GetIncidentApiHandlerArgs) => {};
+const getFieldsHandler = async ({ externalService }: GetCommonFieldsHandlerArgs) => {
+ const res = await externalService.getFields();
+ return res;
+};
const getIncidentTypesHandler = async ({ externalService }: GetIncidentTypesHandlerArgs) => {
const res = await externalService.getIncidentTypes();
return res;
@@ -136,9 +141,10 @@ const pushToServiceHandler = async ({
};
export const api: ExternalServiceApi = {
- handshake: handshakeHandler,
- pushToService: pushToServiceHandler,
+ getFields: getFieldsHandler,
getIncident: getIncidentHandler,
+ handshake: handshakeHandler,
incidentTypes: getIncidentTypesHandler,
+ pushToService: pushToServiceHandler,
severity: getSeverityHandler,
};
diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts
index 53285a2a350af..6203dda4120f5 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts
@@ -25,6 +25,7 @@ import {
ResilientExecutorResultData,
ExecutorSubActionGetIncidentTypesParams,
ExecutorSubActionGetSeverityParams,
+ ExecutorSubActionCommonFieldsParams,
} from './types';
import * as i18n from './translations';
import { Logger } from '../../../../../../src/core/server';
@@ -37,7 +38,7 @@ interface GetActionTypeParams {
configurationUtilities: ActionsConfigurationUtilities;
}
-const supportedSubActions: string[] = ['pushToService', 'incidentTypes', 'severity'];
+const supportedSubActions: string[] = ['getFields', 'pushToService', 'incidentTypes', 'severity'];
// action type definition
export function getActionType(
@@ -122,6 +123,14 @@ async function executor(
logger.debug(`response push to service for incident id: ${data.id}`);
}
+ if (subAction === 'getFields') {
+ const getFieldsParams = subActionParams as ExecutorSubActionCommonFieldsParams;
+ data = await api.getFields({
+ externalService,
+ params: getFieldsParams,
+ });
+ }
+
if (subAction === 'incidentTypes') {
const incidentTypesParams = subActionParams as ExecutorSubActionGetIncidentTypesParams;
data = await api.incidentTypes({
diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/mocks.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/mocks.ts
index 2e841728159a3..2b2a22a66b709 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/resilient/mocks.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/mocks.ts
@@ -8,8 +8,275 @@ import { ExternalService, PushToServiceApiParams, ExecutorSubActionPushParams }
import { MapRecord } from '../case/types';
+export const resilientFields = [
+ {
+ id: 17,
+ name: 'name',
+ text: 'Name',
+ prefix: null,
+ type_id: 0,
+ tooltip: 'A unique name to identify this particular incident.',
+ input_type: 'text',
+ required: 'always',
+ hide_notification: false,
+ chosen: false,
+ default_chosen_by_server: false,
+ blank_option: false,
+ internal: true,
+ uuid: 'ad6ed4f2-8d87-4ba2-81fa-03568a9326cc',
+ operations: [
+ 'equals',
+ 'not_equals',
+ 'contains',
+ 'not_contains',
+ 'changed',
+ 'changed_to',
+ 'not_changed_to',
+ 'has_a_value',
+ 'not_has_a_value',
+ ],
+ operation_perms: {
+ changed_to: {
+ show_in_manual_actions: false,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ },
+ has_a_value: {
+ show_in_manual_actions: true,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ },
+ not_changed_to: {
+ show_in_manual_actions: false,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ },
+ equals: {
+ show_in_manual_actions: true,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ },
+ changed: {
+ show_in_manual_actions: false,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ },
+ contains: {
+ show_in_manual_actions: true,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ },
+ not_contains: {
+ show_in_manual_actions: true,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ },
+ not_equals: {
+ show_in_manual_actions: true,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ },
+ not_has_a_value: {
+ show_in_manual_actions: true,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ },
+ },
+ values: [],
+ perms: {
+ delete: false,
+ modify_name: false,
+ modify_values: false,
+ modify_blank: false,
+ modify_required: false,
+ modify_operations: false,
+ modify_chosen: false,
+ modify_default: false,
+ show_in_manual_actions: true,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ show_in_scripts: true,
+ modify_type: ['text'],
+ sort: true,
+ },
+ read_only: false,
+ changeable: true,
+ rich_text: false,
+ templates: [],
+ deprecated: false,
+ tags: [],
+ calculated: false,
+ is_tracked: false,
+ allow_default_value: false,
+ },
+ {
+ id: 15,
+ name: 'description',
+ text: 'Description',
+ prefix: null,
+ type_id: 0,
+ tooltip: 'A free form text description of the incident.',
+ input_type: 'textarea',
+ hide_notification: false,
+ chosen: false,
+ default_chosen_by_server: false,
+ blank_option: false,
+ internal: true,
+ uuid: '420d70b1-98f9-4681-a20b-84f36a9e5e48',
+ operations: [
+ 'equals',
+ 'not_equals',
+ 'contains',
+ 'not_contains',
+ 'changed',
+ 'changed_to',
+ 'not_changed_to',
+ 'has_a_value',
+ 'not_has_a_value',
+ ],
+ operation_perms: {
+ changed_to: {
+ show_in_manual_actions: false,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ },
+ has_a_value: {
+ show_in_manual_actions: true,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ },
+ not_changed_to: {
+ show_in_manual_actions: false,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ },
+ equals: {
+ show_in_manual_actions: true,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ },
+ changed: {
+ show_in_manual_actions: false,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ },
+ contains: {
+ show_in_manual_actions: true,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ },
+ not_contains: {
+ show_in_manual_actions: true,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ },
+ not_equals: {
+ show_in_manual_actions: true,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ },
+ not_has_a_value: {
+ show_in_manual_actions: true,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ },
+ },
+ values: [],
+ perms: {
+ delete: false,
+ modify_name: false,
+ modify_values: false,
+ modify_blank: false,
+ modify_required: false,
+ modify_operations: false,
+ modify_chosen: false,
+ modify_default: false,
+ show_in_manual_actions: true,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ show_in_scripts: true,
+ modify_type: ['textarea'],
+ sort: true,
+ },
+ read_only: false,
+ changeable: true,
+ rich_text: true,
+ templates: [],
+ deprecated: false,
+ tags: [],
+ calculated: false,
+ is_tracked: false,
+ allow_default_value: false,
+ },
+ {
+ id: 65,
+ name: 'create_date',
+ text: 'Date Created',
+ prefix: null,
+ type_id: 0,
+ tooltip: 'The date the incident was created. This field is read-only.',
+ input_type: 'datetimepicker',
+ hide_notification: false,
+ chosen: false,
+ default_chosen_by_server: false,
+ blank_option: false,
+ internal: true,
+ uuid: 'b4faf728-881a-4e8b-bf0b-d39b720392a1',
+ operations: ['due_within', 'overdue_by', 'has_a_value', 'not_has_a_value'],
+ operation_perms: {
+ has_a_value: {
+ show_in_manual_actions: true,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ },
+ not_has_a_value: {
+ show_in_manual_actions: true,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ },
+ due_within: {
+ show_in_manual_actions: true,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ },
+ overdue_by: {
+ show_in_manual_actions: true,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ },
+ },
+ values: [],
+ perms: {
+ delete: false,
+ modify_name: false,
+ modify_values: false,
+ modify_blank: false,
+ modify_required: false,
+ modify_operations: false,
+ modify_chosen: false,
+ modify_default: false,
+ show_in_manual_actions: true,
+ show_in_auto_actions: true,
+ show_in_notifications: true,
+ show_in_scripts: true,
+ modify_type: ['datetimepicker'],
+ sort: true,
+ },
+ read_only: true,
+ changeable: false,
+ rich_text: false,
+ templates: [],
+ deprecated: false,
+ tags: [],
+ calculated: false,
+ is_tracked: false,
+ allow_default_value: false,
+ },
+];
+
const createMock = (): jest.Mocked => {
const service = {
+ getFields: jest.fn().mockImplementation(() => Promise.resolve(resilientFields)),
getIncident: jest.fn().mockImplementation(() =>
Promise.resolve({
id: '1',
diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/schema.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/schema.ts
index b6e3a9525dfd4..c7ceba94140fb 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/resilient/schema.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/schema.ts
@@ -53,11 +53,16 @@ export const ExecutorSubActionGetIncidentParamsSchema = schema.object({
});
// Reserved for future implementation
+export const ExecutorSubActionCommonFieldsParamsSchema = schema.object({});
export const ExecutorSubActionHandshakeParamsSchema = schema.object({});
export const ExecutorSubActionGetIncidentTypesParamsSchema = schema.object({});
export const ExecutorSubActionGetSeverityParamsSchema = schema.object({});
export const ExecutorParamsSchema = schema.oneOf([
+ schema.object({
+ subAction: schema.literal('getFields'),
+ subActionParams: ExecutorSubActionCommonFieldsParamsSchema,
+ }),
schema.object({
subAction: schema.literal('getIncident'),
subActionParams: ExecutorSubActionGetIncidentParamsSchema,
diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts
index 86ea352625a5b..ecf246cb8fe3c 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts
@@ -11,7 +11,7 @@ import * as utils from '../lib/axios_utils';
import { ExternalService } from './types';
import { Logger } from '../../../../../../src/core/server';
import { loggingSystemMock } from '../../../../../../src/core/server/mocks';
-import { incidentTypes, severity } from './mocks';
+import { incidentTypes, resilientFields, severity } from './mocks';
const logger = loggingSystemMock.create().get() as jest.Mocked;
@@ -231,7 +231,7 @@ describe('IBM Resilient service', () => {
requestMock.mockImplementation(() => {
throw new Error('An error has occurred');
});
- expect(service.getIncident('1')).rejects.toThrow(
+ await expect(service.getIncident('1')).rejects.toThrow(
'Unable to get incident with id 1. Error: An error has occurred'
);
});
@@ -310,7 +310,7 @@ describe('IBM Resilient service', () => {
throw new Error('An error has occurred');
});
- expect(
+ await expect(
service.createIncident({
incident: {
name: 'title',
@@ -418,7 +418,7 @@ describe('IBM Resilient service', () => {
test('it should throw an error', async () => {
mockIncidentUpdate(true);
- expect(
+ await expect(
service.updateIncident({
incidentId: '1',
incident: {
@@ -502,7 +502,7 @@ describe('IBM Resilient service', () => {
throw new Error('An error has occurred');
});
- expect(
+ await expect(
service.createComment({
incidentId: '1',
comment: {
@@ -541,7 +541,7 @@ describe('IBM Resilient service', () => {
throw new Error('An error has occurred');
});
- expect(service.getIncidentTypes()).rejects.toThrow(
+ await expect(service.getIncidentTypes()).rejects.toThrow(
'[Action][IBM Resilient]: Unable to get incident types. Error: An error has occurred.'
);
});
@@ -578,9 +578,40 @@ describe('IBM Resilient service', () => {
throw new Error('An error has occurred');
});
- expect(service.getIncidentTypes()).rejects.toThrow(
+ await expect(service.getIncidentTypes()).rejects.toThrow(
'[Action][IBM Resilient]: Unable to get incident types. Error: An error has occurred.'
);
});
});
+
+ describe('getFields', () => {
+ test('it should call request with correct arguments', async () => {
+ requestMock.mockImplementation(() => ({
+ data: resilientFields,
+ }));
+ await service.getFields();
+
+ expect(requestMock).toHaveBeenCalledWith({
+ axios,
+ logger,
+ url: 'https://resilient.elastic.co/rest/orgs/201/types/incident/fields',
+ });
+ });
+ test('it returns common fields correctly', async () => {
+ requestMock.mockImplementation(() => ({
+ data: resilientFields,
+ }));
+ const res = await service.getFields();
+ expect(res).toEqual(resilientFields);
+ });
+
+ test('it should throw an error', async () => {
+ requestMock.mockImplementation(() => {
+ throw new Error('An error has occurred');
+ });
+ await expect(service.getFields()).rejects.toThrow(
+ 'Unable to get fields. Error: An error has occurred'
+ );
+ });
+ });
});
diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts
index 4bf1453641e42..a13204f8bb1d8 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts
@@ -303,12 +303,27 @@ export const createExternalService = (
}
};
+ const getFields = async () => {
+ try {
+ const res = await request({
+ axios: axiosInstance,
+ url: incidentFieldsUrl,
+ logger,
+ proxySettings,
+ });
+ return res.data ?? [];
+ } catch (error) {
+ throw new Error(getErrorMessage(i18n.NAME, `Unable to get fields. Error: ${error.message}.`));
+ }
+ };
+
return {
- getIncident,
- createIncident,
- updateIncident,
createComment,
+ createIncident,
+ getFields,
+ getIncident,
getIncidentTypes,
getSeverity,
+ updateIncident,
};
};
diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/types.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/types.ts
index ed622ee473b65..a70420b30a092 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/resilient/types.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/types.ts
@@ -8,14 +8,15 @@
import { TypeOf } from '@kbn/config-schema';
import {
- ExternalIncidentServiceConfigurationSchema,
- ExternalIncidentServiceSecretConfigurationSchema,
ExecutorParamsSchema,
- ExecutorSubActionPushParamsSchema,
+ ExecutorSubActionCommonFieldsParamsSchema,
ExecutorSubActionGetIncidentParamsSchema,
- ExecutorSubActionHandshakeParamsSchema,
ExecutorSubActionGetIncidentTypesParamsSchema,
ExecutorSubActionGetSeverityParamsSchema,
+ ExecutorSubActionHandshakeParamsSchema,
+ ExecutorSubActionPushParamsSchema,
+ ExternalIncidentServiceConfigurationSchema,
+ ExternalIncidentServiceSecretConfigurationSchema,
} from './schema';
import { ActionsConfigurationUtilities } from '../../actions_config';
@@ -31,6 +32,10 @@ export type ResilientSecretConfigurationType = TypeOf<
typeof ExternalIncidentServiceSecretConfigurationSchema
>;
+export type ExecutorSubActionCommonFieldsParams = TypeOf<
+ typeof ExecutorSubActionCommonFieldsParamsSchema
+>;
+
export type ExecutorParams = TypeOf;
export type ExecutorSubActionPushParams = TypeOf;
@@ -60,6 +65,14 @@ export interface ExternalServiceCommentResponse {
}
export type ExternalServiceParams = Record;
+export interface ExternalServiceFields {
+ id: string;
+ input_type: string;
+ name: string;
+ read_only: boolean;
+ required?: string;
+}
+export type GetCommonFieldsResponse = ExternalServiceFields[];
export type Incident = Pick<
ExecutorSubActionPushParams,
@@ -86,12 +99,13 @@ export type GetIncidentTypesResponse = Array<{ id: string; name: string }>;
export type GetSeverityResponse = Array<{ id: string; name: string }>;
export interface ExternalService {
- getIncident: (id: string) => Promise;
- createIncident: (params: CreateIncidentParams) => Promise;
- updateIncident: (params: UpdateIncidentParams) => Promise;
createComment: (params: CreateCommentParams) => Promise;
+ createIncident: (params: CreateIncidentParams) => Promise;
+ getFields: () => Promise;
+ getIncident: (id: string) => Promise;
getIncidentTypes: () => Promise;
getSeverity: () => Promise;
+ updateIncident: (params: UpdateIncidentParams) => Promise;
}
export interface PushToServiceApiParams extends ExecutorSubActionPushParams {
@@ -132,6 +146,11 @@ export interface HandshakeApiHandlerArgs extends ExternalServiceApiHandlerArgs {
params: ExecutorSubActionHandshakeParams;
}
+export interface GetCommonFieldsHandlerArgs {
+ externalService: ExternalService;
+ params: ExecutorSubActionCommonFieldsParams;
+}
+
export interface GetIncidentTypesHandlerArgs {
externalService: ExternalService;
params: ExecutorSubActionGetIncidentTypesParams;
@@ -147,6 +166,7 @@ export interface PushToServiceResponse extends ExternalServiceIncidentResponse {
}
export interface ExternalServiceApi {
+ getFields: (args: GetCommonFieldsHandlerArgs) => Promise;
handshake: (args: HandshakeApiHandlerArgs) => Promise;
pushToService: (args: PushToServiceApiHandlerArgs) => Promise;
getIncident: (args: GetIncidentApiHandlerArgs) => Promise;
@@ -156,6 +176,7 @@ export interface ExternalServiceApi {
export type ResilientExecutorResultData =
| PushToServiceResponse
+ | GetCommonFieldsResponse
| GetIncidentTypesResponse
| GetSeverityResponse;
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.test.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.test.ts
index d49c2f265d04f..4683b661e21da 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.test.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.test.ts
@@ -5,7 +5,7 @@
*/
import { Logger } from '../../../../../../src/core/server';
-import { externalServiceMock, mapping, apiParams } from './mocks';
+import { externalServiceMock, mapping, apiParams, serviceNowCommonFields } from './mocks';
import { ExternalService } from './types';
import { api } from './api';
let mockedLogger: jest.Mocked;
@@ -15,634 +15,619 @@ describe('api', () => {
beforeEach(() => {
externalService = externalServiceMock.create();
- jest.clearAllMocks();
});
- afterEach(() => {
- jest.clearAllMocks();
- });
+ describe('create incident', () => {
+ test('it creates an incident', async () => {
+ const params = { ...apiParams, externalId: null };
+ const res = await api.pushToService({
+ externalService,
+ mapping,
+ params,
+ secrets: {},
+ logger: mockedLogger,
+ });
- describe('pushToService', () => {
- describe('create incident', () => {
- test('it creates an incident', async () => {
- const params = { ...apiParams, externalId: null };
- const res = await api.pushToService({
- externalService,
- mapping,
- params,
- secrets: {},
- logger: mockedLogger,
- });
-
- expect(res).toEqual({
- id: 'incident-1',
- title: 'INC01',
- pushedDate: '2020-03-10T12:24:20.000Z',
- url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123',
- comments: [
- {
- commentId: 'case-comment-1',
- pushedDate: '2020-03-10T12:24:20.000Z',
- },
- {
- commentId: 'case-comment-2',
- pushedDate: '2020-03-10T12:24:20.000Z',
- },
- ],
- });
- });
-
- test('it creates an incident without comments', async () => {
- const params = { ...apiParams, externalId: null, comments: [] };
- const res = await api.pushToService({
- externalService,
- mapping,
- params,
- secrets: {},
- logger: mockedLogger,
- });
-
- expect(res).toEqual({
- id: 'incident-1',
- title: 'INC01',
- pushedDate: '2020-03-10T12:24:20.000Z',
- url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123',
- });
- });
-
- test('it calls createIncident correctly', async () => {
- const params = { ...apiParams, externalId: null, comments: [] };
- await api.pushToService({
- externalService,
- mapping,
- params,
- secrets: { username: 'elastic', password: 'elastic' },
- logger: mockedLogger,
- });
-
- expect(externalService.createIncident).toHaveBeenCalledWith({
- incident: {
- severity: '1',
- urgency: '2',
- impact: '3',
- caller_id: 'elastic',
- description:
- 'Incident description (created at 2020-03-13T08:34:53.450Z by Elastic User)',
- short_description:
- 'Incident title (created at 2020-03-13T08:34:53.450Z by Elastic User)',
- },
- });
- expect(externalService.updateIncident).not.toHaveBeenCalled();
- });
-
- test('it calls updateIncident correctly when creating an incident and having comments', async () => {
- const params = { ...apiParams, externalId: null };
- await api.pushToService({
- externalService,
- mapping,
- params,
- secrets: {},
- logger: mockedLogger,
- });
- expect(externalService.updateIncident).toHaveBeenCalledTimes(2);
- expect(externalService.updateIncident).toHaveBeenNthCalledWith(1, {
- incident: {
- severity: '1',
- urgency: '2',
- impact: '3',
- comments: 'A comment (added at 2020-03-13T08:34:53.450Z by Elastic User)',
- description:
- 'Incident description (created at 2020-03-13T08:34:53.450Z by Elastic User)',
- short_description:
- 'Incident title (created at 2020-03-13T08:34:53.450Z by Elastic User)',
+ expect(res).toEqual({
+ id: 'incident-1',
+ title: 'INC01',
+ pushedDate: '2020-03-10T12:24:20.000Z',
+ url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123',
+ comments: [
+ {
+ commentId: 'case-comment-1',
+ pushedDate: '2020-03-10T12:24:20.000Z',
},
- incidentId: 'incident-1',
- });
-
- expect(externalService.updateIncident).toHaveBeenNthCalledWith(2, {
- incident: {
- severity: '1',
- urgency: '2',
- impact: '3',
- comments: 'Another comment (added at 2020-03-13T08:34:53.450Z by Elastic User)',
- description:
- 'Incident description (created at 2020-03-13T08:34:53.450Z by Elastic User)',
- short_description:
- 'Incident title (created at 2020-03-13T08:34:53.450Z by Elastic User)',
+ {
+ commentId: 'case-comment-2',
+ pushedDate: '2020-03-10T12:24:20.000Z',
},
- incidentId: 'incident-1',
- });
+ ],
});
});
- describe('update incident', () => {
- test('it updates an incident', async () => {
- const res = await api.pushToService({
- externalService,
- mapping,
- params: apiParams,
- secrets: {},
- logger: mockedLogger,
- });
-
- expect(res).toEqual({
- id: 'incident-2',
- title: 'INC02',
- pushedDate: '2020-03-10T12:24:20.000Z',
- url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123',
- comments: [
- {
- commentId: 'case-comment-1',
- pushedDate: '2020-03-10T12:24:20.000Z',
- },
- {
- commentId: 'case-comment-2',
- pushedDate: '2020-03-10T12:24:20.000Z',
- },
- ],
- });
- });
-
- test('it updates an incident without comments', async () => {
- const params = { ...apiParams, comments: [] };
- const res = await api.pushToService({
- externalService,
- mapping,
- params,
- secrets: {},
- logger: mockedLogger,
- });
-
- expect(res).toEqual({
- id: 'incident-2',
- title: 'INC02',
- pushedDate: '2020-03-10T12:24:20.000Z',
- url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123',
- });
- });
-
- test('it calls updateIncident correctly', async () => {
- const params = { ...apiParams };
- await api.pushToService({
- externalService,
- mapping,
- params,
- secrets: {},
- logger: mockedLogger,
- });
-
- expect(externalService.updateIncident).toHaveBeenCalledWith({
- incidentId: 'incident-3',
- incident: {
- severity: '1',
- urgency: '2',
- impact: '3',
- description:
- 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
- short_description:
- 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
- },
- });
- expect(externalService.createIncident).not.toHaveBeenCalled();
- });
-
- test('it calls updateIncident to create a comments correctly', async () => {
- const params = { ...apiParams };
- await api.pushToService({
- externalService,
- mapping,
- params,
- secrets: {},
- logger: mockedLogger,
- });
- expect(externalService.updateIncident).toHaveBeenCalledTimes(3);
- expect(externalService.updateIncident).toHaveBeenNthCalledWith(1, {
- incident: {
- severity: '1',
- urgency: '2',
- impact: '3',
- description:
- 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
- short_description:
- 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
- },
- incidentId: 'incident-3',
- });
-
- expect(externalService.updateIncident).toHaveBeenNthCalledWith(2, {
- incident: {
- severity: '1',
- urgency: '2',
- impact: '3',
- comments: 'A comment (added at 2020-03-13T08:34:53.450Z by Elastic User)',
- description:
- 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
- short_description:
- 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
- },
- incidentId: 'incident-2',
- });
+ test('it creates an incident without comments', async () => {
+ const params = { ...apiParams, externalId: null, comments: [] };
+ const res = await api.pushToService({
+ externalService,
+ mapping,
+ params,
+ secrets: {},
+ logger: mockedLogger,
+ });
+
+ expect(res).toEqual({
+ id: 'incident-1',
+ title: 'INC01',
+ pushedDate: '2020-03-10T12:24:20.000Z',
+ url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123',
});
});
- describe('mapping variations', () => {
- test('overwrite & append', async () => {
- mapping.set('title', {
- target: 'short_description',
- actionType: 'overwrite',
- });
-
- mapping.set('description', {
- target: 'description',
- actionType: 'append',
- });
-
- mapping.set('comments', {
- target: 'comments',
- actionType: 'append',
- });
-
- mapping.set('short_description', {
- target: 'title',
- actionType: 'overwrite',
- });
-
- await api.pushToService({
- externalService,
- mapping,
- params: apiParams,
- secrets: {},
- logger: mockedLogger,
- });
- expect(externalService.updateIncident).toHaveBeenCalledWith({
- incidentId: 'incident-3',
- incident: {
- severity: '1',
- urgency: '2',
- impact: '3',
- short_description:
- 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
- description:
- 'description from servicenow \r\nIncident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
- },
- });
- });
-
- test('nothing & append', async () => {
- mapping.set('title', {
- target: 'short_description',
- actionType: 'nothing',
- });
-
- mapping.set('description', {
- target: 'description',
- actionType: 'append',
- });
-
- mapping.set('comments', {
- target: 'comments',
- actionType: 'append',
- });
-
- mapping.set('short_description', {
- target: 'title',
- actionType: 'nothing',
- });
-
- await api.pushToService({
- externalService,
- mapping,
- params: apiParams,
- secrets: {},
- logger: mockedLogger,
- });
- expect(externalService.updateIncident).toHaveBeenCalledWith({
- incidentId: 'incident-3',
- incident: {
- severity: '1',
- urgency: '2',
- impact: '3',
- description:
- 'description from servicenow \r\nIncident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
- },
- });
- });
-
- test('append & append', async () => {
- mapping.set('title', {
- target: 'short_description',
- actionType: 'append',
- });
-
- mapping.set('description', {
- target: 'description',
- actionType: 'append',
- });
-
- mapping.set('comments', {
- target: 'comments',
- actionType: 'append',
- });
-
- mapping.set('short_description', {
- target: 'title',
- actionType: 'append',
- });
-
- await api.pushToService({
- externalService,
- mapping,
- params: apiParams,
- secrets: {},
- logger: mockedLogger,
- });
- expect(externalService.updateIncident).toHaveBeenCalledWith({
- incidentId: 'incident-3',
- incident: {
- severity: '1',
- urgency: '2',
- impact: '3',
- short_description:
- 'title from servicenow \r\nIncident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
- description:
- 'description from servicenow \r\nIncident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
- },
- });
- });
-
- test('nothing & nothing', async () => {
- mapping.set('title', {
- target: 'short_description',
- actionType: 'nothing',
- });
-
- mapping.set('description', {
- target: 'description',
- actionType: 'nothing',
- });
-
- mapping.set('comments', {
- target: 'comments',
- actionType: 'append',
- });
-
- mapping.set('short_description', {
- target: 'title',
- actionType: 'nothing',
- });
-
- await api.pushToService({
- externalService,
- mapping,
- params: apiParams,
- secrets: {},
- logger: mockedLogger,
- });
-
- expect(externalService.updateIncident).toHaveBeenCalledWith({
- incidentId: 'incident-3',
- incident: {
- severity: '1',
- urgency: '2',
- impact: '3',
- },
- });
- });
-
- test('overwrite & nothing', async () => {
- mapping.set('title', {
- target: 'short_description',
- actionType: 'overwrite',
- });
-
- mapping.set('description', {
- target: 'description',
- actionType: 'nothing',
- });
-
- mapping.set('comments', {
- target: 'comments',
- actionType: 'append',
- });
-
- mapping.set('short_description', {
- target: 'title',
- actionType: 'overwrite',
- });
-
- await api.pushToService({
- externalService,
- mapping,
- params: apiParams,
- secrets: {},
- logger: mockedLogger,
- });
- expect(externalService.updateIncident).toHaveBeenCalledWith({
- incidentId: 'incident-3',
- incident: {
- severity: '1',
- urgency: '2',
- impact: '3',
- short_description:
- 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
- },
- });
- });
-
- test('overwrite & overwrite', async () => {
- mapping.set('title', {
- target: 'short_description',
- actionType: 'overwrite',
- });
-
- mapping.set('description', {
- target: 'description',
- actionType: 'overwrite',
- });
-
- mapping.set('comments', {
- target: 'comments',
- actionType: 'append',
- });
-
- mapping.set('short_description', {
- target: 'title',
- actionType: 'overwrite',
- });
-
- await api.pushToService({
- externalService,
- mapping,
- params: apiParams,
- secrets: {},
- logger: mockedLogger,
- });
- expect(externalService.updateIncident).toHaveBeenCalledWith({
- incidentId: 'incident-3',
- incident: {
- severity: '1',
- urgency: '2',
- impact: '3',
- short_description:
- 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
- description:
- 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
- },
- });
- });
-
- test('nothing & overwrite', async () => {
- mapping.set('title', {
- target: 'short_description',
- actionType: 'nothing',
- });
-
- mapping.set('description', {
- target: 'description',
- actionType: 'overwrite',
- });
-
- mapping.set('comments', {
- target: 'comments',
- actionType: 'append',
- });
-
- mapping.set('short_description', {
- target: 'title',
- actionType: 'nothing',
- });
-
- await api.pushToService({
- externalService,
- mapping,
- params: apiParams,
- secrets: {},
- logger: mockedLogger,
- });
- expect(externalService.updateIncident).toHaveBeenCalledWith({
- incidentId: 'incident-3',
- incident: {
- severity: '1',
- urgency: '2',
- impact: '3',
- description:
- 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
- },
- });
- });
-
- test('append & overwrite', async () => {
- mapping.set('title', {
- target: 'short_description',
- actionType: 'append',
- });
-
- mapping.set('description', {
- target: 'description',
- actionType: 'overwrite',
- });
-
- mapping.set('comments', {
- target: 'comments',
- actionType: 'append',
- });
-
- mapping.set('short_description', {
- target: 'title',
- actionType: 'append',
- });
-
- await api.pushToService({
- externalService,
- mapping,
- params: apiParams,
- secrets: {},
- logger: mockedLogger,
- });
- expect(externalService.updateIncident).toHaveBeenCalledWith({
- incidentId: 'incident-3',
- incident: {
- severity: '1',
- urgency: '2',
- impact: '3',
- short_description:
- 'title from servicenow \r\nIncident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
- description:
- 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
+ test('it calls createIncident correctly', async () => {
+ const params = { ...apiParams, externalId: null, comments: [] };
+ await api.pushToService({
+ externalService,
+ mapping,
+ params,
+ secrets: { username: 'elastic', password: 'elastic' },
+ logger: mockedLogger,
+ });
+
+ expect(externalService.createIncident).toHaveBeenCalledWith({
+ incident: {
+ severity: '1',
+ urgency: '2',
+ impact: '3',
+ caller_id: 'elastic',
+ description: 'Incident description (created at 2020-03-13T08:34:53.450Z by Elastic User)',
+ short_description: 'Incident title (created at 2020-03-13T08:34:53.450Z by Elastic User)',
+ },
+ });
+ expect(externalService.updateIncident).not.toHaveBeenCalled();
+ });
+
+ test('it calls updateIncident correctly when creating an incident and having comments', async () => {
+ const params = { ...apiParams, externalId: null };
+ await api.pushToService({
+ externalService,
+ mapping,
+ params,
+ secrets: {},
+ logger: mockedLogger,
+ });
+ expect(externalService.updateIncident).toHaveBeenCalledTimes(2);
+ expect(externalService.updateIncident).toHaveBeenNthCalledWith(1, {
+ incident: {
+ severity: '1',
+ urgency: '2',
+ impact: '3',
+ comments: 'A comment (added at 2020-03-13T08:34:53.450Z by Elastic User)',
+ description: 'Incident description (created at 2020-03-13T08:34:53.450Z by Elastic User)',
+ short_description: 'Incident title (created at 2020-03-13T08:34:53.450Z by Elastic User)',
+ },
+ incidentId: 'incident-1',
+ });
+
+ expect(externalService.updateIncident).toHaveBeenNthCalledWith(2, {
+ incident: {
+ severity: '1',
+ urgency: '2',
+ impact: '3',
+ comments: 'Another comment (added at 2020-03-13T08:34:53.450Z by Elastic User)',
+ description: 'Incident description (created at 2020-03-13T08:34:53.450Z by Elastic User)',
+ short_description: 'Incident title (created at 2020-03-13T08:34:53.450Z by Elastic User)',
+ },
+ incidentId: 'incident-1',
+ });
+ });
+ });
+
+ describe('update incident', () => {
+ test('it updates an incident', async () => {
+ const res = await api.pushToService({
+ externalService,
+ mapping,
+ params: apiParams,
+ secrets: {},
+ logger: mockedLogger,
+ });
+
+ expect(res).toEqual({
+ id: 'incident-2',
+ title: 'INC02',
+ pushedDate: '2020-03-10T12:24:20.000Z',
+ url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123',
+ comments: [
+ {
+ commentId: 'case-comment-1',
+ pushedDate: '2020-03-10T12:24:20.000Z',
},
- });
- });
-
- test('append & nothing', async () => {
- mapping.set('title', {
- target: 'short_description',
- actionType: 'append',
- });
-
- mapping.set('description', {
- target: 'description',
- actionType: 'nothing',
- });
-
- mapping.set('comments', {
- target: 'comments',
- actionType: 'append',
- });
-
- mapping.set('short_description', {
- target: 'title',
- actionType: 'append',
- });
-
- await api.pushToService({
- externalService,
- mapping,
- params: apiParams,
- secrets: {},
- logger: mockedLogger,
- });
- expect(externalService.updateIncident).toHaveBeenCalledWith({
- incidentId: 'incident-3',
- incident: {
- severity: '1',
- urgency: '2',
- impact: '3',
- short_description:
- 'title from servicenow \r\nIncident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
+ {
+ commentId: 'case-comment-2',
+ pushedDate: '2020-03-10T12:24:20.000Z',
},
- });
- });
-
- test('comment nothing', async () => {
- mapping.set('title', {
- target: 'short_description',
- actionType: 'overwrite',
- });
-
- mapping.set('description', {
- target: 'description',
- actionType: 'nothing',
- });
-
- mapping.set('comments', {
- target: 'comments',
- actionType: 'nothing',
- });
-
- mapping.set('short_description', {
- target: 'title',
- actionType: 'overwrite',
- });
-
- await api.pushToService({
- externalService,
- mapping,
- params: apiParams,
- secrets: {},
- logger: mockedLogger,
- });
- expect(externalService.updateIncident).toHaveBeenCalledTimes(1);
+ ],
+ });
+ });
+
+ test('it updates an incident without comments', async () => {
+ const params = { ...apiParams, comments: [] };
+ const res = await api.pushToService({
+ externalService,
+ mapping,
+ params,
+ secrets: {},
+ logger: mockedLogger,
+ });
+
+ expect(res).toEqual({
+ id: 'incident-2',
+ title: 'INC02',
+ pushedDate: '2020-03-10T12:24:20.000Z',
+ url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123',
+ });
+ });
+
+ test('it calls updateIncident correctly', async () => {
+ const params = { ...apiParams };
+ await api.pushToService({
+ externalService,
+ mapping,
+ params,
+ secrets: {},
+ logger: mockedLogger,
+ });
+
+ expect(externalService.updateIncident).toHaveBeenCalledWith({
+ incidentId: 'incident-3',
+ incident: {
+ severity: '1',
+ urgency: '2',
+ impact: '3',
+ description: 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
+ short_description: 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
+ },
+ });
+ expect(externalService.createIncident).not.toHaveBeenCalled();
+ });
+
+ test('it calls updateIncident to create a comments correctly', async () => {
+ const params = { ...apiParams };
+ await api.pushToService({
+ externalService,
+ mapping,
+ params,
+ secrets: {},
+ logger: mockedLogger,
+ });
+ expect(externalService.updateIncident).toHaveBeenCalledTimes(3);
+ expect(externalService.updateIncident).toHaveBeenNthCalledWith(1, {
+ incident: {
+ severity: '1',
+ urgency: '2',
+ impact: '3',
+ description: 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
+ short_description: 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
+ },
+ incidentId: 'incident-3',
+ });
+
+ expect(externalService.updateIncident).toHaveBeenNthCalledWith(2, {
+ incident: {
+ severity: '1',
+ urgency: '2',
+ impact: '3',
+ comments: 'A comment (added at 2020-03-13T08:34:53.450Z by Elastic User)',
+ description: 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
+ short_description: 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
+ },
+ incidentId: 'incident-2',
+ });
+ });
+ });
+
+ describe('mapping variations', () => {
+ test('overwrite & append', async () => {
+ mapping.set('title', {
+ target: 'short_description',
+ actionType: 'overwrite',
+ });
+
+ mapping.set('description', {
+ target: 'description',
+ actionType: 'append',
+ });
+
+ mapping.set('comments', {
+ target: 'comments',
+ actionType: 'append',
+ });
+
+ mapping.set('short_description', {
+ target: 'title',
+ actionType: 'overwrite',
+ });
+
+ await api.pushToService({
+ externalService,
+ mapping,
+ params: apiParams,
+ secrets: {},
+ logger: mockedLogger,
+ });
+ expect(externalService.updateIncident).toHaveBeenCalledWith({
+ incidentId: 'incident-3',
+ incident: {
+ severity: '1',
+ urgency: '2',
+ impact: '3',
+ short_description: 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
+ description:
+ 'description from servicenow \r\nIncident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
+ },
+ });
+ });
+
+ test('nothing & append', async () => {
+ mapping.set('title', {
+ target: 'short_description',
+ actionType: 'nothing',
+ });
+
+ mapping.set('description', {
+ target: 'description',
+ actionType: 'append',
+ });
+
+ mapping.set('comments', {
+ target: 'comments',
+ actionType: 'append',
+ });
+
+ mapping.set('short_description', {
+ target: 'title',
+ actionType: 'nothing',
+ });
+
+ await api.pushToService({
+ externalService,
+ mapping,
+ params: apiParams,
+ secrets: {},
+ logger: mockedLogger,
+ });
+ expect(externalService.updateIncident).toHaveBeenCalledWith({
+ incidentId: 'incident-3',
+ incident: {
+ severity: '1',
+ urgency: '2',
+ impact: '3',
+ description:
+ 'description from servicenow \r\nIncident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
+ },
+ });
+ });
+
+ test('append & append', async () => {
+ mapping.set('title', {
+ target: 'short_description',
+ actionType: 'append',
+ });
+
+ mapping.set('description', {
+ target: 'description',
+ actionType: 'append',
+ });
+
+ mapping.set('comments', {
+ target: 'comments',
+ actionType: 'append',
+ });
+
+ mapping.set('short_description', {
+ target: 'title',
+ actionType: 'append',
+ });
+
+ await api.pushToService({
+ externalService,
+ mapping,
+ params: apiParams,
+ secrets: {},
+ logger: mockedLogger,
+ });
+ expect(externalService.updateIncident).toHaveBeenCalledWith({
+ incidentId: 'incident-3',
+ incident: {
+ severity: '1',
+ urgency: '2',
+ impact: '3',
+ short_description:
+ 'title from servicenow \r\nIncident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
+ description:
+ 'description from servicenow \r\nIncident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
+ },
+ });
+ });
+
+ test('nothing & nothing', async () => {
+ mapping.set('title', {
+ target: 'short_description',
+ actionType: 'nothing',
+ });
+
+ mapping.set('description', {
+ target: 'description',
+ actionType: 'nothing',
+ });
+
+ mapping.set('comments', {
+ target: 'comments',
+ actionType: 'append',
+ });
+
+ mapping.set('short_description', {
+ target: 'title',
+ actionType: 'nothing',
+ });
+
+ await api.pushToService({
+ externalService,
+ mapping,
+ params: apiParams,
+ secrets: {},
+ logger: mockedLogger,
+ });
+
+ expect(externalService.updateIncident).toHaveBeenCalledWith({
+ incidentId: 'incident-3',
+ incident: {
+ severity: '1',
+ urgency: '2',
+ impact: '3',
+ },
+ });
+ });
+
+ test('overwrite & nothing', async () => {
+ mapping.set('title', {
+ target: 'short_description',
+ actionType: 'overwrite',
+ });
+
+ mapping.set('description', {
+ target: 'description',
+ actionType: 'nothing',
+ });
+
+ mapping.set('comments', {
+ target: 'comments',
+ actionType: 'append',
+ });
+
+ mapping.set('short_description', {
+ target: 'title',
+ actionType: 'overwrite',
+ });
+
+ await api.pushToService({
+ externalService,
+ mapping,
+ params: apiParams,
+ secrets: {},
+ logger: mockedLogger,
+ });
+ expect(externalService.updateIncident).toHaveBeenCalledWith({
+ incidentId: 'incident-3',
+ incident: {
+ severity: '1',
+ urgency: '2',
+ impact: '3',
+ short_description: 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
+ },
+ });
+ });
+
+ test('overwrite & overwrite', async () => {
+ mapping.set('title', {
+ target: 'short_description',
+ actionType: 'overwrite',
+ });
+
+ mapping.set('description', {
+ target: 'description',
+ actionType: 'overwrite',
+ });
+
+ mapping.set('comments', {
+ target: 'comments',
+ actionType: 'append',
+ });
+
+ mapping.set('short_description', {
+ target: 'title',
+ actionType: 'overwrite',
+ });
+
+ await api.pushToService({
+ externalService,
+ mapping,
+ params: apiParams,
+ secrets: {},
+ logger: mockedLogger,
+ });
+ expect(externalService.updateIncident).toHaveBeenCalledWith({
+ incidentId: 'incident-3',
+ incident: {
+ severity: '1',
+ urgency: '2',
+ impact: '3',
+ short_description: 'Incident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
+ description: 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
+ },
+ });
+ });
+
+ test('nothing & overwrite', async () => {
+ mapping.set('title', {
+ target: 'short_description',
+ actionType: 'nothing',
+ });
+
+ mapping.set('description', {
+ target: 'description',
+ actionType: 'overwrite',
+ });
+
+ mapping.set('comments', {
+ target: 'comments',
+ actionType: 'append',
+ });
+
+ mapping.set('short_description', {
+ target: 'title',
+ actionType: 'nothing',
+ });
+
+ await api.pushToService({
+ externalService,
+ mapping,
+ params: apiParams,
+ secrets: {},
+ logger: mockedLogger,
+ });
+ expect(externalService.updateIncident).toHaveBeenCalledWith({
+ incidentId: 'incident-3',
+ incident: {
+ severity: '1',
+ urgency: '2',
+ impact: '3',
+ description: 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
+ },
+ });
+ });
+
+ test('append & overwrite', async () => {
+ mapping.set('title', {
+ target: 'short_description',
+ actionType: 'append',
+ });
+
+ mapping.set('description', {
+ target: 'description',
+ actionType: 'overwrite',
+ });
+
+ mapping.set('comments', {
+ target: 'comments',
+ actionType: 'append',
+ });
+
+ mapping.set('short_description', {
+ target: 'title',
+ actionType: 'append',
+ });
+
+ await api.pushToService({
+ externalService,
+ mapping,
+ params: apiParams,
+ secrets: {},
+ logger: mockedLogger,
+ });
+ expect(externalService.updateIncident).toHaveBeenCalledWith({
+ incidentId: 'incident-3',
+ incident: {
+ severity: '1',
+ urgency: '2',
+ impact: '3',
+ short_description:
+ 'title from servicenow \r\nIncident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
+ description: 'Incident description (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
+ },
+ });
+ });
+
+ test('append & nothing', async () => {
+ mapping.set('title', {
+ target: 'short_description',
+ actionType: 'append',
+ });
+
+ mapping.set('description', {
+ target: 'description',
+ actionType: 'nothing',
+ });
+
+ mapping.set('comments', {
+ target: 'comments',
+ actionType: 'append',
+ });
+
+ mapping.set('short_description', {
+ target: 'title',
+ actionType: 'append',
+ });
+
+ await api.pushToService({
+ externalService,
+ mapping,
+ params: apiParams,
+ secrets: {},
+ logger: mockedLogger,
+ });
+ expect(externalService.updateIncident).toHaveBeenCalledWith({
+ incidentId: 'incident-3',
+ incident: {
+ severity: '1',
+ urgency: '2',
+ impact: '3',
+ short_description:
+ 'title from servicenow \r\nIncident title (updated at 2020-03-13T08:34:53.450Z by Elastic User)',
+ },
+ });
+ });
+
+ test('comment nothing', async () => {
+ mapping.set('title', {
+ target: 'short_description',
+ actionType: 'overwrite',
+ });
+
+ mapping.set('description', {
+ target: 'description',
+ actionType: 'nothing',
+ });
+
+ mapping.set('comments', {
+ target: 'comments',
+ actionType: 'nothing',
+ });
+
+ mapping.set('short_description', {
+ target: 'title',
+ actionType: 'overwrite',
+ });
+
+ await api.pushToService({
+ externalService,
+ mapping,
+ params: apiParams,
+ secrets: {},
+ logger: mockedLogger,
+ });
+ expect(externalService.updateIncident).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('getFields', () => {
+ test('it returns the fields correctly', async () => {
+ const res = await api.getFields({
+ externalService,
+ params: {},
});
+ expect(res).toEqual(serviceNowCommonFields);
});
});
});
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.ts
index 6d12a3c92dac7..fbd8fdd635d70 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.ts
@@ -12,6 +12,8 @@ import {
PushToServiceApiParams,
PushToServiceResponse,
Incident,
+ GetCommonFieldsHandlerArgs,
+ GetCommonFieldsResponse,
} from './types';
// TODO: to remove, need to support Case
@@ -127,8 +129,16 @@ const pushToServiceHandler = async ({
return res;
};
+const getFieldsHandler = async ({
+ externalService,
+}: GetCommonFieldsHandlerArgs): Promise => {
+ const res = await externalService.getFields();
+ return res;
+};
+
export const api: ExternalServiceApi = {
+ getFields: getFieldsHandler,
+ getIncident: getIncidentHandler,
handshake: handshakeHandler,
pushToService: pushToServiceHandler,
- getIncident: getIncidentHandler,
};
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts
index 41a577918b18e..d1182b0d3b2fa 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts
@@ -25,6 +25,8 @@ import {
ServiceNowPublicConfigurationType,
ServiceNowSecretConfigurationType,
PushToServiceResponse,
+ ExecutorSubActionCommonFieldsParams,
+ ServiceNowExecutorResultData,
} from './types';
// TODO: to remove, need to support Case
@@ -63,7 +65,7 @@ export function getActionType(
}
// action executor
-
+const supportedSubActions: string[] = ['getFields', 'pushToService'];
async function executor(
{ logger }: { logger: Logger },
execOptions: ActionTypeExecutorOptions<
@@ -71,10 +73,10 @@ async function executor(
ServiceNowSecretConfigurationType,
ExecutorParams
>
-): Promise> {
+): Promise> {
const { actionId, config, params, secrets } = execOptions;
const { subAction, subActionParams } = params;
- let data: PushToServiceResponse | null = null;
+ let data: ServiceNowExecutorResultData | null = null;
const externalService = createExternalService(
{
@@ -91,7 +93,7 @@ async function executor(
throw new Error(errorMessage);
}
- if (subAction !== 'pushToService') {
+ if (!supportedSubActions.includes(subAction)) {
const errorMessage = `[Action][ExternalService] subAction ${subAction} not implemented.`;
logger.error(errorMessage);
throw new Error(errorMessage);
@@ -117,5 +119,13 @@ async function executor(
logger.debug(`response push to service for incident id: ${data.id}`);
}
+ if (subAction === 'getFields') {
+ const getFieldsParams = subActionParams as ExecutorSubActionCommonFieldsParams;
+ data = await api.getFields({
+ externalService,
+ params: getFieldsParams,
+ });
+ }
+
return { status: 'ok', data: data ?? {}, actionId };
}
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/mocks.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/mocks.ts
index 7c2b1bd9d73c1..2351be36a50c4 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/mocks.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/mocks.ts
@@ -7,8 +7,36 @@
import { ExternalService, PushToServiceApiParams, ExecutorSubActionPushParams } from './types';
import { MapRecord } from '../case/types';
+export const serviceNowCommonFields = [
+ {
+ column_label: 'Close notes',
+ max_length: '4000',
+ element: 'close_notes',
+ },
+ {
+ column_label: 'Description',
+ max_length: '4000',
+ element: 'description',
+ },
+ {
+ column_label: 'Short description',
+ max_length: '160',
+ element: 'short_description',
+ },
+ {
+ column_label: 'Created by',
+ max_length: '40',
+ element: 'sys_created_by',
+ },
+ {
+ column_label: 'Updated by',
+ max_length: '40',
+ element: 'sys_updated_by',
+ },
+];
const createMock = (): jest.Mocked => {
const service = {
+ getFields: jest.fn().mockImplementation(() => Promise.resolve(serviceNowCommonFields)),
getIncident: jest.fn().mockImplementation(() =>
Promise.resolve({
short_description: 'title from servicenow',
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/schema.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/schema.ts
index 0dd70ea36636e..77c48aab1f309 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/schema.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/schema.ts
@@ -28,6 +28,7 @@ export const ExternalIncidentServiceSecretConfigurationSchema = schema.object(
);
export const ExecutorSubActionSchema = schema.oneOf([
+ schema.literal('getFields'),
schema.literal('getIncident'),
schema.literal('pushToService'),
schema.literal('handshake'),
@@ -53,8 +54,13 @@ export const ExecutorSubActionGetIncidentParamsSchema = schema.object({
// Reserved for future implementation
export const ExecutorSubActionHandshakeParamsSchema = schema.object({});
+export const ExecutorSubActionCommonFieldsParamsSchema = schema.object({});
export const ExecutorParamsSchema = schema.oneOf([
+ schema.object({
+ subAction: schema.literal('getFields'),
+ subActionParams: ExecutorSubActionCommonFieldsParamsSchema,
+ }),
schema.object({
subAction: schema.literal('getIncident'),
subActionParams: ExecutorSubActionGetIncidentParamsSchema,
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts
index 2adcdf561ce17..8ec80be1e2b09 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts
@@ -11,6 +11,7 @@ import * as utils from '../lib/axios_utils';
import { ExternalService } from './types';
import { Logger } from '../../../../../../src/core/server';
import { loggingSystemMock } from '../../../../../../src/core/server/mocks';
+import { serviceNowCommonFields } from './mocks';
const logger = loggingSystemMock.create().get() as jest.Mocked;
jest.mock('axios');
@@ -108,7 +109,7 @@ describe('ServiceNow service', () => {
requestMock.mockImplementation(() => {
throw new Error('An error has occurred');
});
- expect(service.getIncident('1')).rejects.toThrow(
+ await expect(service.getIncident('1')).rejects.toThrow(
'Unable to get incident with id 1. Error: An error has occurred'
);
});
@@ -155,7 +156,7 @@ describe('ServiceNow service', () => {
throw new Error('An error has occurred');
});
- expect(
+ await expect(
service.createIncident({
incident: { short_description: 'title', description: 'desc' },
})
@@ -207,7 +208,7 @@ describe('ServiceNow service', () => {
throw new Error('An error has occurred');
});
- expect(
+ await expect(
service.updateIncident({
incidentId: '1',
incident: { short_description: 'title', description: 'desc' },
@@ -234,4 +235,36 @@ describe('ServiceNow service', () => {
});
});
});
+
+ describe('getFields', () => {
+ test('it should call request with correct arguments', async () => {
+ requestMock.mockImplementation(() => ({
+ data: { result: serviceNowCommonFields },
+ }));
+ await service.getFields();
+
+ expect(requestMock).toHaveBeenCalledWith({
+ axios,
+ logger,
+ url:
+ 'https://dev102283.service-now.com/api/now/v2/table/sys_dictionary?sysparm_query=name=task^internal_type=string&active=true&read_only=false&sysparm_fields=max_length,element,column_label',
+ });
+ });
+ test('it returns common fields correctly', async () => {
+ requestMock.mockImplementation(() => ({
+ data: { result: serviceNowCommonFields },
+ }));
+ const res = await service.getFields();
+ expect(res).toEqual(serviceNowCommonFields);
+ });
+
+ test('it should throw an error', async () => {
+ requestMock.mockImplementation(() => {
+ throw new Error('An error has occurred');
+ });
+ await expect(service.getFields()).rejects.toThrow(
+ 'Unable to get common fields. Error: An error has occurred'
+ );
+ });
+ });
});
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts
index 9b1da4b4007c6..57f7176e2353c 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts
@@ -16,6 +16,7 @@ import { ProxySettings } from '../../types';
const API_VERSION = 'v2';
const INCIDENT_URL = `api/now/${API_VERSION}/table/incident`;
+const SYS_DICTIONARY = `api/now/${API_VERSION}/table/sys_dictionary`;
// Based on: https://docs.servicenow.com/bundle/orlando-platform-user-interface/page/use/navigation/reference/r_NavigatingByURLExamples.html
const VIEW_INCIDENT_URL = `nav_to.do?uri=incident.do?sys_id=`;
@@ -33,6 +34,7 @@ export const createExternalService = (
}
const incidentUrl = `${url}/${INCIDENT_URL}`;
+ const fieldsUrl = `${url}/${SYS_DICTIONARY}?sysparm_query=name=task^internal_type=string&active=true&read_only=false&sysparm_fields=max_length,element,column_label`;
const axiosInstance = axios.create({
auth: { username, password },
});
@@ -126,10 +128,28 @@ export const createExternalService = (
}
};
+ const getFields = async () => {
+ try {
+ const res = await request({
+ axios: axiosInstance,
+ url: fieldsUrl,
+ logger,
+ proxySettings,
+ });
+
+ return res.data.result.length > 0 ? res.data.result : [];
+ } catch (error) {
+ throw new Error(
+ getErrorMessage(i18n.NAME, `Unable to get common fields. Error: ${error.message}`)
+ );
+ }
+ };
+
return {
- getIncident,
createIncident,
- updateIncident,
findIncidents,
+ getFields,
+ getIncident,
+ updateIncident,
};
};
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts
index a6a0ac946fe96..0ee03f883ec05 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts
@@ -8,12 +8,13 @@
import { TypeOf } from '@kbn/config-schema';
import {
- ExternalIncidentServiceConfigurationSchema,
- ExternalIncidentServiceSecretConfigurationSchema,
ExecutorParamsSchema,
- ExecutorSubActionPushParamsSchema,
+ ExecutorSubActionCommonFieldsParamsSchema,
ExecutorSubActionGetIncidentParamsSchema,
ExecutorSubActionHandshakeParamsSchema,
+ ExecutorSubActionPushParamsSchema,
+ ExternalIncidentServiceConfigurationSchema,
+ ExternalIncidentServiceSecretConfigurationSchema,
} from './schema';
import { ActionsConfigurationUtilities } from '../../actions_config';
import { ExternalServiceCommentResponse } from '../case/types';
@@ -27,6 +28,12 @@ export type ServiceNowSecretConfigurationType = TypeOf<
typeof ExternalIncidentServiceSecretConfigurationSchema
>;
+export type ExecutorSubActionCommonFieldsParams = TypeOf<
+ typeof ExecutorSubActionCommonFieldsParamsSchema
+>;
+
+export type ServiceNowExecutorResultData = PushToServiceResponse | GetCommonFieldsResponse;
+
export interface CreateCommentRequest {
[key: string]: string;
}
@@ -59,6 +66,7 @@ export interface PushToServiceResponse extends ExternalServiceIncidentResponse {
export type ExternalServiceParams = Record;
export interface ExternalService {
+ getFields: () => Promise;
getIncident: (id: string) => Promise;
createIncident: (params: ExternalServiceParams) => Promise;
updateIncident: (params: ExternalServiceParams) => Promise;
@@ -102,8 +110,24 @@ export interface GetIncidentApiHandlerArgs extends ExternalServiceApiHandlerArgs
export interface HandshakeApiHandlerArgs extends ExternalServiceApiHandlerArgs {
params: ExecutorSubActionHandshakeParams;
}
+export interface ExternalServiceFields {
+ column_label: string;
+ name: string;
+ internal_type: {
+ link: string;
+ value: string;
+ };
+ max_length: string;
+ element: string;
+}
+export type GetCommonFieldsResponse = ExternalServiceFields[];
+export interface GetCommonFieldsHandlerArgs {
+ externalService: ExternalService;
+ params: ExecutorSubActionCommonFieldsParams;
+}
export interface ExternalServiceApi {
+ getFields: (args: GetCommonFieldsHandlerArgs) => Promise;
handshake: (args: HandshakeApiHandlerArgs) => Promise;
pushToService: (args: PushToServiceApiHandlerArgs) => Promise;
getIncident: (args: GetIncidentApiHandlerArgs) => Promise;
diff --git a/x-pack/plugins/apm/e2e/cypress/integration/apm.feature b/x-pack/plugins/apm/e2e/cypress/integration/apm.feature
index 285615108266b..494a6b5fadb5b 100644
--- a/x-pack/plugins/apm/e2e/cypress/integration/apm.feature
+++ b/x-pack/plugins/apm/e2e/cypress/integration/apm.feature
@@ -3,5 +3,4 @@ Feature: APM
Scenario: Transaction duration charts
Given a user browses the APM UI application
When the user inspects the opbeans-node service
- Then should redirect to correct path with correct params
- And should have correct y-axis ticks
+ Then should redirect to correct path with correct params
\ No newline at end of file
diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts
index 50c620dca9ddf..42c2bc7ffd318 100644
--- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts
+++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts
@@ -29,16 +29,3 @@ Then(`should redirect to correct path with correct params`, () => {
cy.url().should('contain', `/app/apm/services/opbeans-node/transactions`);
cy.url().should('contain', `transactionType=request`);
});
-
-Then(`should have correct y-axis ticks`, () => {
- const yAxisTick =
- '[data-cy=transaction-duration-charts] .rv-xy-plot__axis--vertical .rv-xy-plot__axis__tick__text';
-
- // wait for all loading to finish
- cy.get('kbnLoadingIndicator').should('not.be.visible');
-
- // literal assertions because snapshot() doesn't retry
- cy.get(yAxisTick).eq(2).should('have.text', '55 ms');
- cy.get(yAxisTick).eq(1).should('have.text', '28 ms');
- cy.get(yAxisTick).eq(0).should('have.text', '0 ms');
-});
diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx
index e17dd9a9eb038..a17bf7e93e466 100644
--- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx
@@ -4,31 +4,24 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import {
+ Axis,
+ Chart,
+ HistogramBarSeries,
+ niceTimeFormatter,
+ Position,
+ ScaleType,
+ Settings,
+ SettingsSpec,
+ TooltipValue,
+} from '@elastic/charts';
import { EuiTitle } from '@elastic/eui';
-import theme from '@elastic/eui/dist/eui_theme_light.json';
-import numeral from '@elastic/numeral';
-import { i18n } from '@kbn/i18n';
import d3 from 'd3';
-import { scaleUtc } from 'd3-scale';
-import { mean } from 'lodash';
import React from 'react';
import { asRelativeDateTimeRange } from '../../../../../common/utils/formatters';
-import { getTimezoneOffsetInMs } from '../../../shared/charts/CustomPlot/getTimezoneOffsetInMs';
-// @ts-expect-error
-import Histogram from '../../../shared/charts/Histogram';
-import { EmptyMessage } from '../../../shared/EmptyMessage';
-
-interface IBucket {
- key: number;
- count: number | undefined;
-}
-
-// TODO: cleanup duplication of this in distribution/get_distribution.ts (ErrorDistributionAPIResponse) and transactions/distribution/index.ts (TransactionDistributionAPIResponse)
-interface IDistribution {
- noHits: boolean;
- buckets: IBucket[];
- bucketSize: number;
-}
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import type { ErrorDistributionAPIResponse } from '../../../../../server/lib/errors/distribution/get_distribution';
+import { useTheme } from '../../../../hooks/useTheme';
interface FormattedBucket {
x0: number;
@@ -37,13 +30,9 @@ interface FormattedBucket {
}
export function getFormattedBuckets(
- buckets: IBucket[],
+ buckets: ErrorDistributionAPIResponse['buckets'],
bucketSize: number
-): FormattedBucket[] | null {
- if (!buckets) {
- return null;
- }
-
+): FormattedBucket[] {
return buckets.map(({ count, key }) => {
return {
x0: key,
@@ -54,76 +43,66 @@ export function getFormattedBuckets(
}
interface Props {
- distribution: IDistribution;
+ distribution: ErrorDistributionAPIResponse;
title: React.ReactNode;
}
-const tooltipHeader = (bucket: FormattedBucket) =>
- asRelativeDateTimeRange(bucket.x0, bucket.x);
-
export function ErrorDistribution({ distribution, title }: Props) {
+ const theme = useTheme();
const buckets = getFormattedBuckets(
distribution.buckets,
distribution.bucketSize
);
- if (!buckets) {
- return (
-
- );
- }
-
- const averageValue = mean(buckets.map((bucket) => bucket.y)) || 0;
const xMin = d3.min(buckets, (d) => d.x0);
- const xMax = d3.max(buckets, (d) => d.x);
- const tickFormat = scaleUtc().domain([xMin, xMax]).tickFormat();
+ const xMax = d3.max(buckets, (d) => d.x0);
+
+ const xFormatter = niceTimeFormatter([xMin, xMax]);
+
+ const tooltipProps: SettingsSpec['tooltip'] = {
+ headerFormatter: (tooltip: TooltipValue) => {
+ const serie = buckets.find((bucket) => bucket.x0 === tooltip.value);
+ if (serie) {
+ return asRelativeDateTimeRange(serie.x0, serie.x);
+ }
+ return `${tooltip.value}`;
+ },
+ };
return (