Skip to content

Commit

Permalink
[7.x] [Alerts] ServiceNow SIR Connector (#88190) (#89982)
Browse files Browse the repository at this point in the history
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
# Conflicts:
#	packages/kbn-optimizer/limits.yml
  • Loading branch information
cnasikas authored Feb 2, 2021
1 parent 944a0c5 commit 669dbc5
Show file tree
Hide file tree
Showing 46 changed files with 2,235 additions and 414 deletions.
2 changes: 1 addition & 1 deletion packages/kbn-optimizer/limits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ pageLoadAssetSize:
tileMap: 65337
timelion: 29920
transform: 41151
triggersActionsUi: 170145
triggersActionsUi: 186732
uiActions: 95074
uiActionsEnhanced: 313011
upgradeAssistant: 80966
Expand Down
54 changes: 36 additions & 18 deletions x-pack/plugins/actions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,14 @@ Table of Contents
- [`params`](#params-6)
- [`subActionParams (pushToService)`](#subactionparams-pushtoservice)
- [`subActionParams (getFields)`](#subactionparams-getfields)
- [`subActionParams (getIncident)`](#subactionparams-getincident)
- [`subActionParams (getChoices)`](#subactionparams-getchoices)
- [Jira](#jira)
- [`config`](#config-7)
- [`secrets`](#secrets-7)
- [`params`](#params-7)
- [`subActionParams (pushToService)`](#subactionparams-pushtoservice-1)
- [`subActionParams (getIncident)`](#subactionparams-getincident)
- [`subActionParams (getIncident)`](#subactionparams-getincident-1)
- [`subActionParams (issueTypes)`](#subactionparams-issuetypes)
- [`subActionParams (fieldsByIssueType)`](#subactionparams-fieldsbyissuetype)
- [`subActionParams (issues)`](#subactionparams-issues)
Expand Down Expand Up @@ -347,17 +349,18 @@ const result = await actionsClient.execute({

Kibana ships with a set of built-in action types:

| Type | Id | Description |
| ------------------------------- | ------------- | ------------------------------------------------------------------ |
| [Server log](#server-log) | `.server-log` | Logs messages to the Kibana log using Kibana's logger |
| [Email](#email) | `.email` | Sends an email using SMTP |
| [Slack](#slack) | `.slack` | Posts a message to a slack channel |
| [Index](#index) | `.index` | Indexes document(s) into Elasticsearch |
| [Webhook](#webhook) | `.webhook` | Send a payload to a web service using HTTP POST or PUT |
| [PagerDuty](#pagerduty) | `.pagerduty` | Trigger, resolve, or acknowlege an incident to a PagerDuty service |
| [ServiceNow](#servicenow) | `.servicenow` | Create or update an incident to a ServiceNow instance |
| [Jira](#jira) | `.jira` | Create or update an issue to a Jira instance |
| [IBM Resilient](#ibm-resilient) | `.resilient` | Create or update an incident to a IBM Resilient instance |
| Type | Id | Description |
| ------------------------------- | ----------------- | ------------------------------------------------------------------ |
| [Server log](#server-log) | `.server-log` | Logs messages to the Kibana log using Kibana's logger |
| [Email](#email) | `.email` | Sends an email using SMTP |
| [Slack](#slack) | `.slack` | Posts a message to a slack channel |
| [Index](#index) | `.index` | Indexes document(s) into Elasticsearch |
| [Webhook](#webhook) | `.webhook` | Send a payload to a web service using HTTP POST or PUT |
| [PagerDuty](#pagerduty) | `.pagerduty` | Trigger, resolve, or acknowlege an incident to a PagerDuty service |
| [ServiceNow ITSM](#servicenow) | `.servicenow` | Create or update an incident to a ServiceNow ITSM instance |
| [ServiceNow SIR](#servicenow) | `.servicenow-sir` | Create or update an incident to a ServiceNow SIR instance |
| [Jira](#jira) | `.jira` | Create or update an issue to a Jira instance |
| [IBM Resilient](#ibm-resilient) | `.resilient` | Create or update an incident to a IBM Resilient instance |

---

Expand Down Expand Up @@ -549,9 +552,11 @@ For more details see [PagerDuty v2 event parameters](https://v2.developer.pagerd

## ServiceNow

ID: `.servicenow`
ServiceNow ITSM ID: `.servicenow`

The ServiceNow action uses the [V2 Table API](https://developer.servicenow.com/app.do#!/rest_api_doc?v=orlando&id=c_TableAPI) to create and update ServiceNow incidents.
ServiceNow SIR ID: `.servicenow-sir`

The ServiceNow actions use the [V2 Table API](https://developer.servicenow.com/app.do#!/rest_api_doc?v=orlando&id=c_TableAPI) to create and update ServiceNow incidents. Both action types use the same `config`, `secrets`, and `params` schema.

### `config`

Expand All @@ -568,10 +573,10 @@ The ServiceNow action uses the [V2 Table API](https://developer.servicenow.com/a

### `params`

| Property | Description | Type |
| --------------- | --------------------------------------------------------------------- | ------ |
| subAction | The sub action to perform. It can be `getFields`, and `pushToService` | string |
| subActionParams | The parameters of the sub action | object |
| Property | Description | Type |
| --------------- | -------------------------------------------------------------------------------------------------- | ------ |
| subAction | The sub action to perform. It can be `pushToService`, `getFields`, `getIncident`, and `getChoices` | string |
| subActionParams | The parameters of the sub action | object |

#### `subActionParams (pushToService)`

Expand All @@ -595,6 +600,19 @@ The following table describes the properties of the `incident` object.

No parameters for `getFields` sub-action. Provide an empty object `{}`.

#### `subActionParams (getIncident)`

| Property | Description | Type |
| ---------- | ------------------------------------- | ------ |
| externalId | The id of the incident in ServiceNow. | string |


#### `subActionParams (getChoices)`

| Property | Description | Type |
| -------- | ------------------------------------------------------------ | -------- |
| fields | An array of fields. Example: `[priority, category, impact]`. | string[] |

---

## Jira
Expand Down
8 changes: 5 additions & 3 deletions x-pack/plugins/actions/server/builtin_action_types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { getActionType as getPagerDutyActionType } from './pagerduty';
import { getActionType as getServerLogActionType } from './server_log';
import { getActionType as getSlackActionType } from './slack';
import { getActionType as getWebhookActionType } from './webhook';
import { getActionType as getServiceNowActionType } from './servicenow';
import { getServiceNowITSMActionType, getServiceNowSIRActionType } from './servicenow';
import { getActionType as getJiraActionType } from './jira';
import { getActionType as getResilientActionType } from './resilient';
import { getActionType as getTeamsActionType } from './teams';
Expand All @@ -38,7 +38,8 @@ export {
} from './webhook';
export {
ActionParamsType as ServiceNowActionParams,
ActionTypeId as ServiceNowActionTypeId,
ServiceNowITSMActionTypeId,
ServiceNowSIRActionTypeId,
} from './servicenow';
export { ActionParamsType as JiraActionParams, ActionTypeId as JiraActionTypeId } from './jira';
export {
Expand Down Expand Up @@ -66,7 +67,8 @@ export function registerBuiltInActionTypes({
actionTypeRegistry.register(getServerLogActionType({ logger }));
actionTypeRegistry.register(getSlackActionType({ logger, configurationUtilities }));
actionTypeRegistry.register(getWebhookActionType({ logger, configurationUtilities }));
actionTypeRegistry.register(getServiceNowActionType({ logger, configurationUtilities }));
actionTypeRegistry.register(getServiceNowITSMActionType({ logger, configurationUtilities }));
actionTypeRegistry.register(getServiceNowSIRActionType({ logger, configurationUtilities }));
actionTypeRegistry.register(getJiraActionType({ logger, configurationUtilities }));
actionTypeRegistry.register(getResilientActionType({ logger, configurationUtilities }));
actionTypeRegistry.register(getTeamsActionType({ logger, configurationUtilities }));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import { Logger } from '../../../../../../src/core/server';
import { externalServiceMock, apiParams, serviceNowCommonFields } from './mocks';
import { externalServiceMock, apiParams, serviceNowCommonFields, serviceNowChoices } from './mocks';
import { ExternalService } from './types';
import { api } from './api';
let mockedLogger: jest.Mocked<Logger>;
Expand Down Expand Up @@ -235,4 +235,14 @@ describe('api', () => {
expect(res).toEqual(serviceNowCommonFields);
});
});

describe('getChoices', () => {
test('it returns the fields correctly', async () => {
const res = await api.getChoices({
externalService,
params: { fields: ['priority'] },
});
expect(res).toEqual(serviceNowChoices);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
*/
import {
ExternalServiceApi,
GetChoicesHandlerArgs,
GetChoicesResponse,
GetCommonFieldsHandlerArgs,
GetCommonFieldsResponse,
GetIncidentApiHandlerArgs,
Expand Down Expand Up @@ -71,7 +73,16 @@ const getFieldsHandler = async ({
return res;
};

const getChoicesHandler = async ({
externalService,
params,
}: GetChoicesHandlerArgs): Promise<GetChoicesResponse> => {
const res = await externalService.getChoices(params.fields);
return res;
};

export const api: ExternalServiceApi = {
getChoices: getChoicesHandler,
getFields: getFieldsHandler,
getIncident: getIncidentHandler,
handshake: handshakeHandler,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import { validate } from './validators';
import {
ExternalIncidentServiceConfiguration,
ExternalIncidentServiceSecretConfiguration,
ExecutorParamsSchema,
ExecutorParamsSchemaITSM,
ExecutorParamsSchemaSIR,
} from './schema';
import { ActionsConfigurationUtilities } from '../../actions_config';
import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../../types';
Expand All @@ -27,18 +28,26 @@ import {
PushToServiceResponse,
ExecutorSubActionCommonFieldsParams,
ServiceNowExecutorResultData,
ExecutorSubActionGetChoicesParams,
} from './types';

export type ActionParamsType = TypeOf<typeof ExecutorParamsSchema>;
export type ActionParamsType =
| TypeOf<typeof ExecutorParamsSchemaITSM>
| TypeOf<typeof ExecutorParamsSchemaSIR>;

interface GetActionTypeParams {
logger: Logger;
configurationUtilities: ActionsConfigurationUtilities;
}

export const ActionTypeId = '.servicenow';
const serviceNowITSMTable = 'incident';
const serviceNowSIRTable = 'sn_si_incident';

export const ServiceNowITSMActionTypeId = '.servicenow';
export const ServiceNowSIRActionTypeId = '.servicenow-sir';

// action type definition
export function getActionType(
export function getServiceNowITSMActionType(
params: GetActionTypeParams
): ActionType<
ServiceNowPublicConfigurationType,
Expand All @@ -48,29 +57,56 @@ export function getActionType(
> {
const { logger, configurationUtilities } = params;
return {
id: ActionTypeId,
id: ServiceNowITSMActionTypeId,
minimumLicenseRequired: 'platinum',
name: i18n.NAME,
name: i18n.SERVICENOW_ITSM,
validate: {
config: schema.object(ExternalIncidentServiceConfiguration, {
validate: curry(validate.config)(configurationUtilities),
}),
secrets: schema.object(ExternalIncidentServiceSecretConfiguration, {
validate: curry(validate.secrets)(configurationUtilities),
}),
params: ExecutorParamsSchema,
params: ExecutorParamsSchemaITSM,
},
executor: curry(executor)({ logger, configurationUtilities }),
executor: curry(executor)({ logger, configurationUtilities, table: serviceNowITSMTable }),
};
}

export function getServiceNowSIRActionType(
params: GetActionTypeParams
): ActionType<
ServiceNowPublicConfigurationType,
ServiceNowSecretConfigurationType,
ExecutorParams,
PushToServiceResponse | {}
> {
const { logger, configurationUtilities } = params;
return {
id: ServiceNowSIRActionTypeId,
minimumLicenseRequired: 'platinum',
name: i18n.SERVICENOW_SIR,
validate: {
config: schema.object(ExternalIncidentServiceConfiguration, {
validate: curry(validate.config)(configurationUtilities),
}),
secrets: schema.object(ExternalIncidentServiceSecretConfiguration, {
validate: curry(validate.secrets)(configurationUtilities),
}),
params: ExecutorParamsSchemaSIR,
},
executor: curry(executor)({ logger, configurationUtilities, table: serviceNowSIRTable }),
};
}

// action executor
const supportedSubActions: string[] = ['getFields', 'pushToService'];
const supportedSubActions: string[] = ['getFields', 'pushToService', 'getChoices', 'getIncident'];
async function executor(
{
logger,
configurationUtilities,
}: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities },
table,
}: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities; table: string },
execOptions: ActionTypeExecutorOptions<
ServiceNowPublicConfigurationType,
ServiceNowSecretConfigurationType,
Expand All @@ -82,6 +118,7 @@ async function executor(
let data: ServiceNowExecutorResultData | null = null;

const externalService = createExternalService(
table,
{
config,
secrets,
Expand Down Expand Up @@ -122,5 +159,13 @@ async function executor(
});
}

if (subAction === 'getChoices') {
const getChoicesParams = subActionParams as ExecutorSubActionGetChoicesParams;
data = await api.getChoices({
externalService,
params: getChoicesParams,
});
}

return { status: 'ok', data: data ?? {}, actionId };
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { ExternalService, PushToServiceApiParams, ExecutorSubActionPushParams } from './types';
import { ExternalService, ExecutorSubActionPushParams } from './types';

export const serviceNowCommonFields = [
{
Expand Down Expand Up @@ -33,8 +33,43 @@ export const serviceNowCommonFields = [
element: 'sys_updated_by',
},
];

export const serviceNowChoices = [
{
dependent_value: '',
label: '1 - Critical',
value: '1',
element: 'priority',
},
{
dependent_value: '',
label: '2 - High',
value: '2',
element: 'priority',
},
{
dependent_value: '',
label: '3 - Moderate',
value: '3',
element: 'priority',
},
{
dependent_value: '',
label: '4 - Low',
value: '4',
element: 'priority',
},
{
dependent_value: '',
label: '5 - Planning',
value: '5',
element: 'priority',
},
];

const createMock = (): jest.Mocked<ExternalService> => {
const service = {
getChoices: jest.fn().mockImplementation(() => Promise.resolve(serviceNowChoices)),
getFields: jest.fn().mockImplementation(() => Promise.resolve(serviceNowCommonFields)),
getIncident: jest.fn().mockImplementation(() =>
Promise.resolve({
Expand Down Expand Up @@ -89,8 +124,6 @@ const executorParams: ExecutorSubActionPushParams = {
],
};

const apiParams: PushToServiceApiParams = {
...executorParams,
};
const apiParams = executorParams;

export { externalServiceMock, executorParams, apiParams };
Loading

0 comments on commit 669dbc5

Please sign in to comment.