From b8c127c18fbf70ba0c6b73f9f86bcef5d712beee Mon Sep 17 00:00:00 2001 From: ymao1 Date: Thu, 3 Jun 2021 09:35:27 -0400 Subject: [PATCH] Fixing pagerduty server side functionality (#101091) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../builtin_action_types/pagerduty.test.ts | 206 +++++++++++++++++- .../server/builtin_action_types/pagerduty.ts | 17 +- 2 files changed, 213 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts index 93c5dd4a44db..7540785714bc 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts @@ -152,14 +152,15 @@ describe('validateParams()', () => { `); }); - test('should validate and throw error when timestamp has spaces', () => { + test('should validate and pass when valid timestamp has spaces', () => { const randoDate = new Date('1963-09-23T01:23:45Z').toISOString(); const timestamp = ` ${randoDate}`; - expect(() => { - validateParams(actionType, { - timestamp, - }); - }).toThrowError(`error validating action params: error parsing timestamp "${timestamp}"`); + expect(validateParams(actionType, { timestamp })).toEqual({ timestamp }); + }); + + test('should validate and pass when timestamp is empty string', () => { + const timestamp = ''; + expect(validateParams(actionType, { timestamp })).toEqual({ timestamp }); }); test('should validate and throw error when timestamp is invalid', () => { @@ -409,7 +410,7 @@ describe('execute()', () => { `); }); - test('should fail when sendPagerdury throws', async () => { + test('should fail when sendPagerduty throws', async () => { const secrets = { routingKey: 'super-secret' }; const config = { apiUrl: null }; const params = {}; @@ -576,4 +577,195 @@ describe('execute()', () => { } `); }); + + test('should succeed when timestamp contains valid date and extraneous spaces', async () => { + const randoDate = new Date('1963-09-23T01:23:45Z').toISOString(); + const secrets = { + routingKey: 'super-secret', + }; + const config = { + apiUrl: 'the-api-url', + }; + const params: ActionParamsType = { + eventAction: 'trigger', + dedupKey: 'a-dedup-key', + summary: 'the summary', + source: 'the-source', + severity: 'critical', + timestamp: ` ${randoDate} `, + component: 'the-component', + group: 'the-group', + class: 'the-class', + }; + + postPagerdutyMock.mockImplementation(() => { + return { status: 202, data: 'data-here' }; + }); + + const actionId = 'some-action-id'; + const executorOptions: PagerDutyActionTypeExecutorOptions = { + actionId, + config, + params, + secrets, + services, + }; + const actionResponse = await actionType.executor(executorOptions); + const { apiUrl, data, headers } = postPagerdutyMock.mock.calls[0][0]; + expect({ apiUrl, data, headers }).toMatchInlineSnapshot(` + Object { + "apiUrl": "the-api-url", + "data": Object { + "dedup_key": "a-dedup-key", + "event_action": "trigger", + "payload": Object { + "class": "the-class", + "component": "the-component", + "group": "the-group", + "severity": "critical", + "source": "the-source", + "summary": "the summary", + "timestamp": "1963-09-23T01:23:45.000Z", + }, + }, + "headers": Object { + "Content-Type": "application/json", + "X-Routing-Key": "super-secret", + }, + } + `); + expect(actionResponse).toMatchInlineSnapshot(` + Object { + "actionId": "some-action-id", + "data": "data-here", + "status": "ok", + } + `); + }); + + test('should not pass timestamp field when timestamp is empty string', async () => { + const secrets = { + routingKey: 'super-secret', + }; + const config = { + apiUrl: 'the-api-url', + }; + const params: ActionParamsType = { + eventAction: 'trigger', + dedupKey: 'a-dedup-key', + summary: 'the summary', + source: 'the-source', + severity: 'critical', + timestamp: '', + component: 'the-component', + group: 'the-group', + class: 'the-class', + }; + + postPagerdutyMock.mockImplementation(() => { + return { status: 202, data: 'data-here' }; + }); + + const actionId = 'some-action-id'; + const executorOptions: PagerDutyActionTypeExecutorOptions = { + actionId, + config, + params, + secrets, + services, + }; + const actionResponse = await actionType.executor(executorOptions); + const { apiUrl, data, headers } = postPagerdutyMock.mock.calls[0][0]; + expect({ apiUrl, data, headers }).toMatchInlineSnapshot(` + Object { + "apiUrl": "the-api-url", + "data": Object { + "dedup_key": "a-dedup-key", + "event_action": "trigger", + "payload": Object { + "class": "the-class", + "component": "the-component", + "group": "the-group", + "severity": "critical", + "source": "the-source", + "summary": "the summary", + }, + }, + "headers": Object { + "Content-Type": "application/json", + "X-Routing-Key": "super-secret", + }, + } + `); + expect(actionResponse).toMatchInlineSnapshot(` + Object { + "actionId": "some-action-id", + "data": "data-here", + "status": "ok", + } + `); + }); + + test('should not pass timestamp field when timestamp is string of spaces', async () => { + const secrets = { + routingKey: 'super-secret', + }; + const config = { + apiUrl: 'the-api-url', + }; + const params: ActionParamsType = { + eventAction: 'trigger', + dedupKey: 'a-dedup-key', + summary: 'the summary', + source: 'the-source', + severity: 'critical', + timestamp: ' ', + component: 'the-component', + group: 'the-group', + class: 'the-class', + }; + + postPagerdutyMock.mockImplementation(() => { + return { status: 202, data: 'data-here' }; + }); + + const actionId = 'some-action-id'; + const executorOptions: PagerDutyActionTypeExecutorOptions = { + actionId, + config, + params, + secrets, + services, + }; + const actionResponse = await actionType.executor(executorOptions); + const { apiUrl, data, headers } = postPagerdutyMock.mock.calls[0][0]; + expect({ apiUrl, data, headers }).toMatchInlineSnapshot(` + Object { + "apiUrl": "the-api-url", + "data": Object { + "dedup_key": "a-dedup-key", + "event_action": "trigger", + "payload": Object { + "class": "the-class", + "component": "the-component", + "group": "the-group", + "severity": "critical", + "source": "the-source", + "summary": "the summary", + }, + }, + "headers": Object { + "Content-Type": "application/json", + "X-Routing-Key": "super-secret", + }, + } + `); + expect(actionResponse).toMatchInlineSnapshot(` + Object { + "actionId": "some-action-id", + "data": "data-here", + "status": "ok", + } + `); + }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts index b64cf6ec346d..5d83b658111e 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts @@ -85,11 +85,19 @@ const ParamsSchema = schema.object( { validate: validateParams } ); +function validateTimestamp(timestamp?: string): string | null { + if (timestamp) { + return timestamp.trim().length > 0 ? timestamp.trim() : null; + } + return null; +} + function validateParams(paramsObject: unknown): string | void { const { timestamp, eventAction, dedupKey } = paramsObject as ActionParamsType; - if (timestamp != null) { + const validatedTimestamp = validateTimestamp(timestamp); + if (validatedTimestamp != null) { try { - const date = Date.parse(timestamp); + const date = Date.parse(validatedTimestamp); if (isNaN(date)) { return i18n.translate('xpack.actions.builtin.pagerduty.invalidTimestampErrorMessage', { defaultMessage: `error parsing timestamp "{timestamp}"`, @@ -279,11 +287,14 @@ function getBodyForEventAction(actionId: string, params: ActionParamsType): Page return data; } + const validatedTimestamp = validateTimestamp(params.timestamp); + data.payload = { summary: params.summary || 'No summary provided.', source: params.source || `Kibana Action ${actionId}`, severity: params.severity || 'info', - ...omitBy(pick(params, ['timestamp', 'component', 'group', 'class']), isUndefined), + ...(validatedTimestamp ? { timestamp: validatedTimestamp } : {}), + ...omitBy(pick(params, ['component', 'group', 'class']), isUndefined), }; return data;