diff --git a/x-pack/plugins/stack_connectors/server/connector_types/lib/convert_timestamp.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/convert_timestamp.test.ts new file mode 100644 index 0000000000000..1ff43c2e225d3 --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/convert_timestamp.test.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { convertTimestamp } from './convert_timestamp'; + +describe('convert_timestamp', () => { + const stringDate = '2024-09-06T11:10:24.681Z'; + const stringDateWithSlash = '2019/05/15'; + const stringDateWithDot = '10.12.1979'; + const anyString = 'asdfgh'; + const anyStringWithNumber = '123asdfghjkl'; + const epochDate = 1725880672; + const stringifiedEpochDate = '1725880672'; + + it('should return a string date as it is', () => { + expect(convertTimestamp(stringDate)).toBe(stringDate); + }); + + it('should return any string as it is', () => { + expect(convertTimestamp(anyString)).toBe(anyString); + }); + + it('should return any string date with slash as it is', () => { + expect(convertTimestamp(stringDateWithSlash)).toBe(stringDateWithSlash); + }); + + it('should return any string date with dot as it is', () => { + expect(convertTimestamp(stringDateWithDot)).toBe(stringDateWithDot); + }); + + it('should return any string with some numbers in it as it is', () => { + expect(convertTimestamp(anyStringWithNumber)).toBe(anyStringWithNumber); + }); + + it('should return a number if the input is a stringified number', () => { + expect(convertTimestamp('12345678')).toBe(12345678); + }); + + it('should return an epoch date as it is', () => { + expect(convertTimestamp(epochDate)).toBe(epochDate); + }); + + it('should return a stringified epoch date as number', () => { + expect(convertTimestamp(stringifiedEpochDate)).toBe(epochDate); + }); + + it('should return null if timestamp is not passed', () => { + expect(convertTimestamp()).toBe(null); + }); + + it('should return null if timestamp is null', () => { + expect(convertTimestamp(null)).toBe(null); + }); +}); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/lib/convert_timestamp.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/convert_timestamp.ts new file mode 100644 index 0000000000000..3df7c18704a95 --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/convert_timestamp.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export function convertTimestamp(timestamp?: string | number | null): string | number | null { + if (timestamp) { + if (typeof timestamp === 'string') { + const trimmedTimestamp = timestamp.trim(); + if (trimmedTimestamp.length > 0) { + const parsedTimestamp = parseInt(trimmedTimestamp, 10); + + if (!isNaN(parsedTimestamp) && JSON.stringify(parsedTimestamp) === trimmedTimestamp) { + return parsedTimestamp; // return converted epoch + } + return trimmedTimestamp; // return string + } + } + if (typeof timestamp === 'number') { + return timestamp; // return epoch + } + } + return null; +} diff --git a/x-pack/plugins/stack_connectors/server/connector_types/pagerduty/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/pagerduty/index.ts index c4d2444540cad..15d72d78a511b 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/pagerduty/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/pagerduty/index.ts @@ -21,6 +21,7 @@ import { SecurityConnectorFeatureId, } from '@kbn/actions-plugin/common/types'; import { postPagerduty } from './post_pagerduty'; +import { convertTimestamp } from '../lib/convert_timestamp'; // uses the PagerDuty Events API v2 // https://v2.developer.pagerduty.com/docs/events-api-v2 @@ -99,19 +100,12 @@ export 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; - const validatedTimestamp = validateTimestamp(timestamp); - if (validatedTimestamp != null) { + const convertedTimestamp = convertTimestamp(timestamp); + if (convertedTimestamp != null) { try { - const date = moment(validatedTimestamp); + const date = moment(convertedTimestamp); if (!date.isValid()) { return i18n.translate('xpack.stackConnectors.pagerduty.invalidTimestampErrorMessage', { defaultMessage: `error parsing timestamp "{timestamp}"`, @@ -327,13 +321,13 @@ function getBodyForEventAction(actionId: string, params: ActionParamsType): Page return data; } - const validatedTimestamp = validateTimestamp(params.timestamp); + const convertedTimestamp = convertTimestamp(params.timestamp); data.payload = { summary: params.summary || 'No summary provided.', source: params.source || `Kibana Action ${actionId}`, severity: params.severity || 'info', - ...(validatedTimestamp ? { timestamp: moment(validatedTimestamp).toISOString() } : {}), + ...(convertedTimestamp ? { timestamp: moment(convertedTimestamp).toISOString() } : {}), ...omitBy(pick(params, ['component', 'group', 'class']), isUndefined), ...(params.customDetails ? { custom_details: params.customDetails } : {}), }; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/servicenow_itom/api.ts b/x-pack/plugins/stack_connectors/server/connector_types/servicenow_itom/api.ts index c68f1d4419eb1..eb417cf8e353d 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/servicenow_itom/api.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/servicenow_itom/api.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { convertTimestamp } from '../lib/convert_timestamp'; import { api as commonApi } from '../lib/servicenow/api'; import { ExecutorSubActionAddEventParams, @@ -15,8 +16,9 @@ import { const isValidDate = (d: Date) => !isNaN(d.valueOf()); const formatTimeOfEvent = (timeOfEvent: string | null): string | undefined => { - if (timeOfEvent != null) { - const date = new Date(timeOfEvent); + const convertedTimestamp = convertTimestamp(timeOfEvent); + if (convertedTimestamp != null) { + const date = new Date(convertedTimestamp); return isValidDate(date) ? // The format is: yyyy-MM-dd HH:mm:ss GMT