From 8bcd1a1d5018dec33af22a9402cfd3c21d540502 Mon Sep 17 00:00:00 2001 From: Tiago Barbosa Date: Sat, 16 Dec 2023 00:18:09 +0000 Subject: [PATCH 1/4] feat(ux): :sparkles: adds new router to query PD API adds new router to query PD API to support better ux for custom action Signed-off-by: Tiago Barbosa --- src/apis/pagerduty.test.ts | 136 +++++++++++++++++++++++++++++--- src/apis/pagerduty.ts | 52 +++++++++++- src/index.ts | 2 +- src/run.ts | 16 ++-- src/service/router.test.ts | 97 +++++++++++++++++++++++ src/service/router.ts | 53 +++++++++++++ src/service/standaloneServer.ts | 38 +++++++++ src/types.ts | 27 ++++++- 8 files changed, 396 insertions(+), 25 deletions(-) create mode 100644 src/service/router.test.ts create mode 100644 src/service/router.ts create mode 100644 src/service/standaloneServer.ts diff --git a/src/apis/pagerduty.test.ts b/src/apis/pagerduty.test.ts index cd86375..bb50556 100644 --- a/src/apis/pagerduty.test.ts +++ b/src/apis/pagerduty.test.ts @@ -1,5 +1,6 @@ /* eslint-disable jest/no-conditional-expect */ -import { createService, createServiceIntegration } from "./pagerduty"; +import { HttpError } from "../types"; +import { createService, createServiceIntegration, getAllEscalationPolicies } from "./pagerduty"; describe("PagerDuty API", () => { afterEach(() => { @@ -141,14 +142,15 @@ describe("PagerDuty API", () => { global.fetch = jest.fn(() => Promise.resolve({ status: 400, - json: () => Promise.resolve({}) }) ) as jest.Mock; + const expectedErrorMessage = "Failed to create service integration. Caller provided invalid arguments."; + try { await createServiceIntegration(serviceId, vendorId); } catch (error) { - expect(((error as Error).message)).toEqual("Failed to create service integration. Caller provided invalid arguments."); + expect(((error as Error).message)).toEqual(expectedErrorMessage); } }); @@ -158,15 +160,16 @@ describe("PagerDuty API", () => { global.fetch = jest.fn(() => Promise.resolve({ - status: 401, - json: () => Promise.resolve({}) + status: 401 }) ) as jest.Mock; + const expectedErrorMessage = "Failed to create service integration. Caller did not supply credentials or did not provide the correct credentials."; + try { await createServiceIntegration(serviceId, vendorId); } catch (error) { - expect(((error as Error).message)).toEqual("Failed to create service integration. Caller did not supply credentials or did not provide the correct credentials."); + expect(((error as Error).message)).toEqual(expectedErrorMessage); } }); @@ -176,15 +179,16 @@ describe("PagerDuty API", () => { global.fetch = jest.fn(() => Promise.resolve({ - status: 403, - json: () => Promise.resolve({}) + status: 403 }) ) as jest.Mock; + const expectedErrorMessage = "Failed to create service integration. Caller is not authorized to view the requested resource."; + try { await createServiceIntegration(serviceId, vendorId); } catch (error) { - expect(((error as Error).message)).toEqual("Failed to create service integration. Caller is not authorized to view the requested resource."); + expect(((error as Error).message)).toEqual(expectedErrorMessage); } }); @@ -194,15 +198,123 @@ describe("PagerDuty API", () => { global.fetch = jest.fn(() => Promise.resolve({ - status: 429, - json: () => Promise.resolve({}) + status: 429 }) ) as jest.Mock; + const expectedErrorMessage = "Failed to create service integration. Rate limit exceeded."; + try { await createServiceIntegration(serviceId, vendorId); } catch (error) { - expect(((error as Error).message)).toEqual("Failed to create service integration. Rate limit exceeded."); + expect(((error as Error).message)).toEqual(expectedErrorMessage); + } + }); + }); + + describe("getAllEscalationPolicies", () => { + it("should return ok", async () => { + const expectedId = "P0L1CY1D"; + const expectedName = "Test Escalation Policy"; + + const expectedResponse = [ + { + id: expectedId, + name: expectedName + } + ]; + + global.fetch = jest.fn(() => + Promise.resolve({ + status: 200, + json: () => Promise.resolve({ + escalation_policies: [ + { + id: expectedId, + name: expectedName, + } + ] + }) + }) + ) as jest.Mock; + + const result = await getAllEscalationPolicies(); + + expect(result).toEqual(expectedResponse); + expect(result.length).toEqual(1); + expect(fetch).toHaveBeenCalledTimes(1); + }); + + it("should NOT list escalation policies when caller provides invalid arguments", async () => { + global.fetch = jest.fn(() => + Promise.resolve({ + status: 400, + json: () => Promise.resolve({}) + }) + ) as jest.Mock; + + const expectedStatusCode = 400; + const expectedErrorMessage = "Failed to list escalation policies. Caller provided invalid arguments."; + + try { + await getAllEscalationPolicies(); + } catch (error) { + expect(((error as HttpError).status)).toEqual(expectedStatusCode); + expect(((error as HttpError).message)).toEqual(expectedErrorMessage); + } + }); + + it("should NOT list escalation policies when correct credentials are not provided", async () => { + global.fetch = jest.fn(() => + Promise.resolve({ + status: 401 + }) + ) as jest.Mock; + + const expectedStatusCode = 401; + const expectedErrorMessage = "Failed to list escalation policies. Caller did not supply credentials or did not provide the correct credentials."; + + try { + await getAllEscalationPolicies(); + } catch (error) { + expect(((error as HttpError).status)).toEqual(expectedStatusCode); + expect(((error as HttpError).message)).toEqual(expectedErrorMessage); + } + }); + + it("should NOT list escalation policies when account does not have abilities to perform the action", async () => { + global.fetch = jest.fn(() => + Promise.resolve({ + status: 403 + }) + ) as jest.Mock; + + const expectedStatusCode = 403; + const expectedErrorMessage = "Failed to list escalation policies. Caller is not authorized to view the requested resource."; + + try { + await getAllEscalationPolicies(); + } catch (error) { + expect(((error as HttpError).status)).toEqual(expectedStatusCode); + expect(((error as HttpError).message)).toEqual(expectedErrorMessage); + } + }); + + it("should NOT list escalation policies when user is not allowed to view the requested resource", async () => { + global.fetch = jest.fn(() => + Promise.resolve({ + status: 429 + }) + ) as jest.Mock; + + const expectedStatusCode = 429; + const expectedErrorMessage = "Failed to list escalation policies. Rate limit exceeded."; + + try { + await getAllEscalationPolicies(); + } catch (error) { + expect(((error as HttpError).status)).toEqual(expectedStatusCode); + expect(((error as HttpError).message)).toEqual(expectedErrorMessage); } }); }); diff --git a/src/apis/pagerduty.ts b/src/apis/pagerduty.ts index 62dc41d..18b368d 100644 --- a/src/apis/pagerduty.ts +++ b/src/apis/pagerduty.ts @@ -1,6 +1,8 @@ -import { PagerDutyCreateIntegrationResponse, PagerDutyCreateServiceResponse } from "../types"; +import { PagerDutyCreateIntegrationResponse, PagerDutyCreateServiceResponse, PagerDutyEscalationPolicyListResponse, PagerDutyEscalationPolicy, HttpError } from "../types"; -export async function createService(name: string, description: string, escalationPolicyId: string): Promise<[string, string]> { +// Supporting custom actions + +export async function createService(name: string, description: string, escalationPolicyId: string): Promise<[string, string]> { let response: Response; const baseUrl = 'https://api.pagerduty.com/services'; const options: RequestInit = { @@ -14,7 +16,7 @@ export async function createService(name: string, description: string, escalatio }, }), headers: { - Authorization: `Token token=${process.env.PAGERDUTY_TOKEN}`, + Authorization: `Token token=${process.env.PAGERDUTY_TOKEN}`, 'Accept': 'application/vnd.pagerduty+json;version=2', 'Content-Type': 'application/json', }, @@ -103,3 +105,47 @@ export async function createServiceIntegration(serviceId: string, vendorId: stri throw new Error(`Failed to parse service information: ${error}`); } } + +// Supporting router + +export async function getAllEscalationPolicies(): Promise { + let response: Response; + const baseUrl = 'https://api.pagerduty.com/escalation_policies'; + const options: RequestInit = { + method: 'GET', + headers: { + Authorization: `Token token=${process.env.PAGERDUTY_TOKEN}`, + 'Accept': 'application/vnd.pagerduty+json;version=2', + 'Content-Type': 'application/json', + }, + }; + + try { + response = await fetch(`${baseUrl}?total=true&sort_by=name&limit=100`, options); + } catch (error) { + throw new Error(`Failed to retrieve escalation policies: ${error}`); + } + + switch (response.status) { + case 400: + throw new HttpError("Failed to list escalation policies. Caller provided invalid arguments.", 400); + case 401: + throw new HttpError("Failed to list escalation policies. Caller did not supply credentials or did not provide the correct credentials.", 401); + case 403: + throw new HttpError("Failed to list escalation policies. Caller is not authorized to view the requested resource.", 403); + case 429: + throw new HttpError("Failed to list escalation policies. Rate limit exceeded.", 429); + default: // 200 + break; + } + + let result: PagerDutyEscalationPolicyListResponse; + try { + result = await response.json(); + + return result.escalation_policies; + + } catch (error) { + throw new Error(`Failed to parse escalation policy information: ${error}`); + } +} diff --git a/src/index.ts b/src/index.ts index 91aeb8c..e1f65ef 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,2 @@ -// export * from './service/router'; +export * from './service/router'; export * from './actions/custom'; diff --git a/src/run.ts b/src/run.ts index 8e2658a..79c6bd6 100644 --- a/src/run.ts +++ b/src/run.ts @@ -1,15 +1,15 @@ import { getRootLogger } from '@backstage/backend-common'; -// import yn from 'yn'; -// import { startStandaloneServer } from './service/standaloneServer'; +import yn from 'yn'; +import { startStandaloneServer } from './service/standaloneServer'; -// const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007; -// const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); +const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007; +const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); const logger = getRootLogger(); -// startStandaloneServer({ port, enableCors, logger }).catch(err => { -// logger.error(err); -// process.exit(1); -// }); +startStandaloneServer({ port, enableCors, logger }).catch(err => { + logger.error(err); + process.exit(1); +}); process.on('SIGINT', () => { logger.info('CTRL+C pressed; exiting.'); diff --git a/src/service/router.test.ts b/src/service/router.test.ts new file mode 100644 index 0000000..d99c76f --- /dev/null +++ b/src/service/router.test.ts @@ -0,0 +1,97 @@ +import { getVoidLogger } from '@backstage/backend-common'; +import { ConfigReader } from '@backstage/config'; +import express from 'express'; +import request from 'supertest'; + +import { createRouter } from './router'; +import { PagerDutyEscalationPolicy } from '../types'; + +describe('createRouter', () => { + let app: express.Express; + + beforeAll(async () => { + const router = await createRouter( + { + logger: getVoidLogger(), + config: new ConfigReader({ + app: { + baseUrl: 'https://example.com/extra-path', + }, + pagerDuty: { + apiToken: `${process.env.PAGERDUTY_TOKEN}`, + }, + }), + } + ); + app = express().use(router); + }); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe('GET /health', () => { + it('returns ok', async () => { + const response = await request(app).get('/health'); + + expect(response.status).toEqual(200); + expect(response.body).toEqual({ status: 'ok' }); + }); + }); + + describe('GET /escalation_policies', () => { + it('returns ok', async () => { + global.fetch = jest.fn(() => + Promise.resolve({ + status: 200, + json: () => Promise.resolve({ + escalation_policies: [ + { + id: "12345", + name: "Test Escalation Policy", + type: "escalation_policy", + summary: "Test Escalation Policy", + self: "https://api.pagerduty.com/escalation_policies/12345", + html_url: "https://example.pagerduty.com/escalation_policies/12345", + } + ] + }) + }) + ) as jest.Mock; + + const expectedStatusCode = 200; + const expectedResponse = [ + { + label: "Test Escalation Policy", + value: "12345", + } + ]; + + const response = await request(app).get('/escalation_policies'); + + const policies: PagerDutyEscalationPolicy[] = JSON.parse(response.text); + + expect(response.status).toEqual(expectedStatusCode); + expect(response.body).toEqual(expectedResponse); + expect(policies.length).toEqual(1); + }); + + it('returns unauthorized', async () => { + global.fetch = jest.fn(() => + Promise.resolve({ + status: 401 + }) + ) as jest.Mock; + + const expectedStatusCode = 401; + const expectedErrorMessage = "Failed to list escalation policies. Caller did not supply credentials or did not provide the correct credentials."; + + const response = await request(app).get('/escalation_policies'); + + expect(response.status).toEqual(expectedStatusCode); + expect(response.text).toMatch(expectedErrorMessage); + }); + + + }); +}); \ No newline at end of file diff --git a/src/service/router.ts b/src/service/router.ts new file mode 100644 index 0000000..48d2039 --- /dev/null +++ b/src/service/router.ts @@ -0,0 +1,53 @@ +import { errorHandler } from '@backstage/backend-common'; +import { Config } from '@backstage/config'; +import express from 'express'; +import Router from 'express-promise-router'; +import { Logger } from 'winston'; +import { getAllEscalationPolicies } from '../apis/pagerduty'; +import { HttpError } from '../types'; + +export interface RouterOptions { + logger: Logger; + config: Config; +} + +export async function createRouter( + options: RouterOptions +): Promise { + const { logger, config } = options; + + if (config.getOptionalString('pagerDuty.apiToken') === undefined) { + throw new Error('No PagerDuty API token was provided.'); + } + process.env.PAGERDUTY_TOKEN = `${config.getOptionalString('pagerDuty.apiToken')}`; + + const router = Router(); + router.use(express.json()); + + router.get('/escalation_policies', async (_, response) => { + logger.info('Getting escalation policies'); + + try { + const escalationPolicyList = await getAllEscalationPolicies(); + const escalationPolicyDropDownOptions = escalationPolicyList.map((policy) => { + return { + label: policy.name, + value: policy.id, + }; + }); + + response.json(escalationPolicyDropDownOptions); + } catch (error) { + if (error instanceof HttpError) { + response.status(error.status).json(`${error.message}`); + } + } + }); + + router.get('/health', async (_, response) => { + response.status(200).json({ status: 'ok' }); + }); + + router.use(errorHandler()); + return router; +} \ No newline at end of file diff --git a/src/service/standaloneServer.ts b/src/service/standaloneServer.ts new file mode 100644 index 0000000..f60e2a0 --- /dev/null +++ b/src/service/standaloneServer.ts @@ -0,0 +1,38 @@ +import { createServiceBuilder, loadBackendConfig } from '@backstage/backend-common'; +import { Server } from 'http'; +import { Logger } from 'winston'; +import { createRouter } from './router'; + +export interface ServerOptions { + port: number; + enableCors: boolean; + logger: Logger; +} + +export async function startStandaloneServer( + options: ServerOptions, +): Promise { + const logger = options.logger.child({ service: '@pagerduty/backstage-plugin-backend' }); + const config = await loadBackendConfig({ logger, argv: process.argv }); + logger.debug('Starting application server...'); + const router = await createRouter( + { + logger, + config, + } + ); + + let service = createServiceBuilder(module) + .setPort(options.port) + .addRouter('/pagerduty', router); + if (options.enableCors) { + service = service.enableCors({ origin: 'http://localhost:3000' }); + } + + return await service.start().catch(err => { + logger.error(err); + process.exit(1); + }); +} + +module.hot?.accept(); \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index b3f7c44..62e9126 100644 --- a/src/types.ts +++ b/src/types.ts @@ -28,6 +28,7 @@ export type PagerDutyTeam = { export type PagerDutyEscalationPolicy = { id: string; + name: string; type: string; summary: string; self: string; @@ -70,4 +71,28 @@ export type PagerDutyVendor = { summary: string; self: string; htmlUrl: string; -}; \ No newline at end of file +}; + +export type PagerDutyEscalationPolicyListResponse = { + escalation_policies: PagerDutyEscalationPolicy[]; + limit: number; + offset: number; + more: boolean; + total: number; +}; + +export type PagerDutyEscalationPolicyDropDownOption = { + label: string; + value: string; +}; + +export class HttpError extends Error { + constructor(message: string, status: number) { + super(message); + this.status = status; + } + + status: number; +} + + From b0d171492164ee47354f5c0e14d9f47b4a822bde Mon Sep 17 00:00:00 2001 From: Tiago Barbosa Date: Mon, 18 Dec 2023 23:21:01 +0000 Subject: [PATCH 2/4] refactor(schema): :wrench: add config file schema add config file schema to enforce input parameters Signed-off-by: Tiago Barbosa --- config.d.ts | 33 +++++++++++++++++++++++++++++++++ package.json | 4 +++- 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 config.d.ts diff --git a/config.d.ts b/config.d.ts new file mode 100644 index 0000000..13e8d16 --- /dev/null +++ b/config.d.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export interface Config { + /** + * Configuration for the PagerDuty plugin + * @visibility frontend + */ + pagerDuty?: { + /** + * Optional Events Base URL to override the default. + * @visibility frontend + */ + eventsBaseUrl?: string; + /** + * Optional PagerDuty API Token used in API calls from the backend component. + * @visibility frontend + */ + apiToken?: string; + }; +} diff --git a/package.json b/package.json index 5a38e37..7ef021c 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,9 @@ "typescript": "^4.8.4" }, "files": [ - "dist" + "dist", + "config.d.ts" ], + "configSchema": "config.d.ts", "packageManager": "yarn@3.6.3" } From fc3184757eed037b9467eff9d390bfcc3779a03e Mon Sep 17 00:00:00 2001 From: Tiago Barbosa Date: Mon, 18 Dec 2023 23:22:33 +0000 Subject: [PATCH 3/4] feat(drop-down): add api route for getting escalation policies add api route for getting escalation policies. supports frontend. Signed-off-by: Tiago Barbosa --- src/apis/pagerduty.test.ts | 143 ++++++++++++++++++++++++++++++++++++- src/apis/pagerduty.ts | 29 ++++++-- src/service/router.test.ts | 26 ++++++- src/service/router.ts | 23 ++++-- 4 files changed, 205 insertions(+), 16 deletions(-) diff --git a/src/apis/pagerduty.test.ts b/src/apis/pagerduty.test.ts index bb50556..cbe1f99 100644 --- a/src/apis/pagerduty.test.ts +++ b/src/apis/pagerduty.test.ts @@ -219,7 +219,7 @@ describe("PagerDuty API", () => { const expectedResponse = [ { - id: expectedId, + id: expectedId, name: expectedName } ]; @@ -317,6 +317,147 @@ describe("PagerDuty API", () => { expect(((error as HttpError).message)).toEqual(expectedErrorMessage); } }); + + it("should work with pagination", async () => { + const expectedId = ["P0L1CY1D1", "P0L1CY1D2", "P0L1CY1D3", "P0L1CY1D4", "P0L1CY1D5", "P0L1CY1D6", "P0L1CY1D7", "P0L1CY1D8", "P0L1CY1D9", "P0L1CY1D10"]; + const expectedName = ["Test Escalation Policy 1", "Test Escalation Policy 2", "Test Escalation Policy 3", "Test Escalation Policy 4", "Test Escalation Policy 5", "Test Escalation Policy 6", "Test Escalation Policy 7", "Test Escalation Policy 8", "Test Escalation Policy 9", "Test Escalation Policy 10"]; + + const expectedResponse = [ + { + id: expectedId[0], + name: expectedName[0] + }, + { + id: expectedId[1], + name: expectedName[1] + }, + { + id: expectedId[2], + name: expectedName[2] + }, + { + id: expectedId[3], + name: expectedName[3] + }, + { + id: expectedId[4], + name: expectedName[4] + }, + { + id: expectedId[5], + name: expectedName[5] + }, + { + id: expectedId[6], + name: expectedName[6] + }, + { + id: expectedId[7], + name: expectedName[7] + }, + { + id: expectedId[8], + name: expectedName[8] + }, + { + id: expectedId[9], + name: expectedName[9] + } + ]; + + global.fetch = jest.fn().mockReturnValueOnce( + Promise.resolve({ + status: 200, + json: () => Promise.resolve({ + escalation_policies: [ + { + id: expectedId[0], + name: expectedName[0], + }, + { + id: expectedId[1], + name: expectedName[1], + } + ], + more: true + }) + }) + ).mockReturnValueOnce( + Promise.resolve({ + status: 200, + json: () => Promise.resolve({ + escalation_policies: [ + { + id: expectedId[2], + name: expectedName[2], + }, + { + id: expectedId[3], + name: expectedName[3], + } + ], + more: true + }) + }) + ).mockReturnValueOnce( + Promise.resolve({ + status: 200, + json: () => Promise.resolve({ + escalation_policies: [ + { + id: expectedId[4], + name: expectedName[4], + }, + { + id: expectedId[5], + name: expectedName[5], + } + ], + more: true + }) + }) + ).mockReturnValueOnce( + Promise.resolve({ + status: 200, + json: () => Promise.resolve({ + escalation_policies: [ + { + id: expectedId[6], + name: expectedName[6], + }, + { + id: expectedId[7], + name: expectedName[7], + } + ], + more: true + }) + }) + ).mockReturnValueOnce( + Promise.resolve({ + status: 200, + json: () => Promise.resolve({ + escalation_policies: [ + { + id: expectedId[8], + name: expectedName[8], + }, + { + id: expectedId[9], + name: expectedName[9], + } + ], + more: false + }) + }) + ) as jest.Mock; + + const result = await getAllEscalationPolicies(); + + expect(result).toEqual(expectedResponse); + expect(result.length).toEqual(10); + expect(fetch).toHaveBeenCalledTimes(5); + }); }); }); diff --git a/src/apis/pagerduty.ts b/src/apis/pagerduty.ts index 18b368d..4ec4ab4 100644 --- a/src/apis/pagerduty.ts +++ b/src/apis/pagerduty.ts @@ -108,9 +108,8 @@ export async function createServiceIntegration(serviceId: string, vendorId: stri // Supporting router -export async function getAllEscalationPolicies(): Promise { +async function getEscalationPolicies(offset: number, limit: number): Promise<[Boolean, PagerDutyEscalationPolicy[]]> { let response: Response; - const baseUrl = 'https://api.pagerduty.com/escalation_policies'; const options: RequestInit = { method: 'GET', headers: { @@ -119,9 +118,10 @@ export async function getAllEscalationPolicies(): Promise { + const limit = 50; + try { + const res = await getEscalationPolicies(offset, limit); + const results = res[1]; + + // if more results exist + if (res[0]) { + return results.concat((await getAllEscalationPolicies(offset + limit))); + } + + return results; } catch (error) { - throw new Error(`Failed to parse escalation policy information: ${error}`); + + throw new HttpError(`${((error as HttpError).message) }`, ((error as HttpError).status)); } } diff --git a/src/service/router.test.ts b/src/service/router.test.ts index d99c76f..bd05b33 100644 --- a/src/service/router.test.ts +++ b/src/service/router.test.ts @@ -28,7 +28,7 @@ describe('createRouter', () => { beforeEach(() => { jest.resetAllMocks(); - }); + }); describe('GET /health', () => { it('returns ok', async () => { @@ -53,7 +53,7 @@ describe('createRouter', () => { summary: "Test Escalation Policy", self: "https://api.pagerduty.com/escalation_policies/12345", html_url: "https://example.pagerduty.com/escalation_policies/12345", - } + } ] }) }) @@ -91,7 +91,27 @@ describe('createRouter', () => { expect(response.status).toEqual(expectedStatusCode); expect(response.text).toMatch(expectedErrorMessage); }); - + it('returns empty list when no escalation policies exist', async () => { + global.fetch = jest.fn(() => + Promise.resolve({ + status: 200, + json: () => Promise.resolve({ + escalation_policies: [] + }) + }) + ) as jest.Mock; + + const expectedStatusCode = 200; + const expectedResponse: PagerDutyEscalationPolicy[] = []; + + const response = await request(app).get('/escalation_policies'); + + const policies: PagerDutyEscalationPolicy[] = JSON.parse(response.text); + + expect(response.status).toEqual(expectedStatusCode); + expect(response.body).toEqual(expectedResponse); + expect(policies.length).toEqual(0); + }); }); }); \ No newline at end of file diff --git a/src/service/router.ts b/src/service/router.ts index 48d2039..088c381 100644 --- a/src/service/router.ts +++ b/src/service/router.ts @@ -16,17 +16,22 @@ export async function createRouter( ): Promise { const { logger, config } = options; - if (config.getOptionalString('pagerDuty.apiToken') === undefined) { - throw new Error('No PagerDuty API token was provided.'); + // Set the PagerDuty API token as an environment variable if it exists in the config file + try { + process.env.PAGERDUTY_TOKEN = config.getString('pagerDuty.apiToken'); } - process.env.PAGERDUTY_TOKEN = `${config.getOptionalString('pagerDuty.apiToken')}`; - + catch (error) { + logger.error(`Failed to retrieve PagerDuty API token from config file: ${error}`); + throw error; + } + + // Create the router const router = Router(); router.use(express.json()); - router.get('/escalation_policies', async (_, response) => { - logger.info('Getting escalation policies'); - + // Add routes + // GET /escalation_policies + router.get('/escalation_policies', async (_, response) => { try { const escalationPolicyList = await getAllEscalationPolicies(); const escalationPolicyDropDownOptions = escalationPolicyList.map((policy) => { @@ -44,10 +49,14 @@ export async function createRouter( } }); + // GET /health router.get('/health', async (_, response) => { response.status(200).json({ status: 'ok' }); }); + // Add error handler router.use(errorHandler()); + + // Return the router return router; } \ No newline at end of file From 5de08d455ff150190c5c881328f85100de85effd Mon Sep 17 00:00:00 2001 From: Tiago Barbosa Date: Tue, 19 Dec 2023 09:58:55 +0000 Subject: [PATCH 4/4] docs: updated wrong link to documentation updated wrong link to documentation Signed-off-by: Tiago Barbosa --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index cc77809..61ff3d8 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,7 @@ yarn add --cwd packages/backend @pagerduty/backstage-plugin-backend ### Configuration -To use the custom actions as part of your custom project templates follow the instructions on the `Create PagerDuty service with Software Templates` section of the project's documentation [here](https://pagerduty.github.io/backstage-plugin-docs/). - +To use the custom actions as part of your custom project templates follow the instructions on the `Create PagerDuty service with Software Templates` section of the project's documentation [here](https://pagerduty.github.io/backstage-plugin-docs/advanced/create-service-software-template/). ## Support