From f7614ee6fce099c4e50403f979fdcbf38956193a Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Thu, 5 Nov 2020 11:17:19 +0100 Subject: [PATCH] add utility api and use it in pluign --- plugins/pagerduty/src/api/pagerDutyClient.ts | 178 ++++++++++-------- .../pagerduty/src/components/Incidents.tsx | 4 +- .../src/components/PagerDutyServiceCard.tsx | 66 +++---- .../src/components/TriggerDialog.tsx | 5 +- plugins/pagerduty/src/plugin.ts | 18 +- 5 files changed, 149 insertions(+), 122 deletions(-) diff --git a/plugins/pagerduty/src/api/pagerDutyClient.ts b/plugins/pagerduty/src/api/pagerDutyClient.ts index dbdc6622f4..09abaf856c 100644 --- a/plugins/pagerduty/src/api/pagerDutyClient.ts +++ b/plugins/pagerduty/src/api/pagerDutyClient.ts @@ -14,92 +14,78 @@ * limitations under the License. */ -const API_URL = 'https://api.pagerduty.com'; -const EVENTS_API_URL = 'https://events.pagerduty.com/v2'; +import { createApiRef } from '@backstage/core'; +import { + Service, + Incident, + Options, + PagerDutyClientConfig, + ServicesResponse, + IncidentResponse, + OncallsResponse, + Oncall, +} from '../components/types'; -type Options = { - method: string; - headers: { - 'Content-Type': string; - Accept: string; - Authorization?: string; - }; - body?: string; -}; +export const pagerDutyApiRef = createApiRef({ + id: 'plugin.pagerduty.api', + description: 'Used to fetch data from PagerDuty API', +}); -const request = async ( - url: string, - options: any, //Options, -): Promise => { - const response = await fetch(url, options); +interface PagerDutyClient { + getServiceByIntegrationKey(integrationKey: string): Promise; + getIncidentsByServiceId(serviceId: string): Promise; + getOncallByPolicyId(policyId: string): Promise; +} - if (!response.ok) { - const payload = await response.json(); - const errors = payload.errors.map((error: string) => error).join(' '); - const message = `Request failed with ${response.status}, ${errors}`; +export class PagerDutyClientApi implements PagerDutyClient { + private API_URL = 'https://api.pagerduty.com'; + private EVENTS_API_URL = 'https://events.pagerduty.com/v2'; - throw new Error(message); + constructor(private readonly config?: PagerDutyClientConfig) {} + + async getServiceByIntegrationKey(integrationKey: string): Promise { + if (!this.config?.token) { + throw new Error('Missing token'); + } + + const params = `include[]=integrations&include[]=escalation_policies&query=${integrationKey}`; + const url = `${this.API_URL}/services?${params}`; + const { services } = await this.getByUrl(url); + + return services; } - return await response.json(); -}; + async getIncidentsByServiceId(serviceId: string): Promise { + if (!this.config?.token) { + throw new Error('Missing token'); + } -const getByUrl = async (url: string, token: string) => { - const options = { - method: 'GET', - headers: { - Authorization: `Token token=${token}`, - Accept: 'application/vnd.pagerduty+json;version=2', - 'Content-Type': 'application/json', - }, - }; - return await request(url, options); -}; + const params = `service_ids[]=${serviceId}`; + const url = `${this.API_URL}/incidents?${params}`; + const { incidents } = await this.getByUrl(url); -export const getServiceByIntegrationKey = async ( - integrationKey: string, - token: string, -) => { - const response = await getByUrl( - `${API_URL}/services?include[]=integrations&include[]=escalation_policies&query=${integrationKey}`, - token, - ); - if (response.services.length > 1) { - throw new Error('More than one service in response'); + return incidents; } - return response.services[0]; -}; -export const getIncidentsByServiceId = async ( - serviceId: string, - token: string, -) => { - return await getByUrl( - `${API_URL}/incidents?service_ids[]=${serviceId}`, - token, - ); -}; + async getOncallByPolicyId(policyId: string): Promise { + if (!this.config?.token) { + throw new Error('Missing token'); + } -export const getOncallByPolicyId = async (policyId: string, token: string) => { - return await getByUrl( - `${API_URL}/oncalls?include[]=users&escalation_policy_ids[]=${policyId}`, - token, - ); -}; + const params = `include[]=users&escalation_policy_ids[]=${policyId}`; + const url = `${this.API_URL}/oncalls?${params}`; + const { oncalls } = await this.getByUrl(url); -export function triggerPagerDutyAlarm( - integrationKey: string, - source: string, - description: string, - userName: string, -) { - const options = { - method: 'POST', - headers: { - 'Content-Type': 'application/json; charset=UTF-8', - Accept: 'application/json, text/plain, */*', - }, - body: JSON.stringify({ + return oncalls; + } + + triggerPagerDutyAlarm( + integrationKey: string, + source: string, + description: string, + userName: string, + ) { + const body = JSON.stringify({ event_action: 'trigger', routing_key: integrationKey, client: 'Backstage', @@ -113,8 +99,46 @@ export function triggerPagerDutyAlarm( user: userName, }, }, - }), - }; + }); - return request(`${EVENTS_API_URL}/enqueue`, options); + const options = { + method: 'POST', + headers: { + 'Content-Type': 'application/json; charset=UTF-8', + Accept: 'application/json, text/plain, */*', + }, + body, + }; + + return this.request(`${this.EVENTS_API_URL}/enqueue`, options); + } + + private async getByUrl(url: string): Promise { + const options = { + method: 'GET', + headers: { + Authorization: `Token token=${this.config!.token}`, + Accept: 'application/vnd.pagerduty+json;version=2', + 'Content-Type': 'application/json', + }, + }; + const response = await this.request(url, options); + + return response.json(); + } + + private async request(url: string, options: Options): Promise { + try { + const response = await fetch(url, options); + if (!response.ok) { + const payload = await response.json(); + const errors = payload.errors.map((error: string) => error).join(' '); + const message = `Request failed with ${response.status}, ${errors}`; + throw new Error(message); + } + return response; + } catch (error) { + throw new Error(error); + } + } } diff --git a/plugins/pagerduty/src/components/Incidents.tsx b/plugins/pagerduty/src/components/Incidents.tsx index 0117281a0e..dd3301904d 100644 --- a/plugins/pagerduty/src/components/Incidents.tsx +++ b/plugins/pagerduty/src/components/Incidents.tsx @@ -114,7 +114,9 @@ type IncidentsProps = { export const Incidents = ({ incidents }: IncidentsProps) => ( Incidents}> {incidents.length ? ( - incidents.map(incident => ) + incidents.map((incident, index) => ( + + )) ) : ( )} diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx index e17e35a996..54713b8ecd 100644 --- a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx +++ b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx @@ -14,18 +14,15 @@ * limitations under the License. */ import React from 'react'; -import { InfoCard, useApi, configApiRef } from '@backstage/core'; +import { InfoCard, useApi } from '@backstage/core'; import { Entity } from '@backstage/catalog-model'; import { Grid, LinearProgress } from '@material-ui/core'; import { Incidents } from './Incidents'; import { EscalationPolicy } from './Escalation'; import { TriggerButton } from './TriggerButton'; import { useAsync } from 'react-use'; -import { - getServiceByIntegrationKey, - getIncidentsByServiceId, - getOncallByPolicyId, -} from '../api/pagerDutyClient'; +import { Alert } from '@material-ui/lab'; +import { pagerDutyApiRef } from '../api/pagerDutyClient'; export const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key'; @@ -37,65 +34,52 @@ type Props = { }; export const PagerDutyServiceCard = ({ entity }: Props) => { - const configApi = useApi(configApiRef); - - // TODO: handle missing token - const token = configApi.getOptionalString('pagerduty.api.token') ?? undefined; - console.log({ token }); + const api = useApi(pagerDutyApiRef); const { value, loading, error } = useAsync(async () => { const integrationKey = entity.metadata.annotations![ PAGERDUTY_INTEGRATION_KEY ]; - const service = await getServiceByIntegrationKey(integrationKey, token); - const incidents = await getIncidentsByServiceId( - (service as any).id /*// TODO: fix type */, - token, - ); - const oncalls = await getOncallByPolicyId( - (service as any).escalation_policy.id, // TODO: fix type - token, - ); + const services = await api.getServiceByIntegrationKey(integrationKey); + // TODO check services length + const service = services[0]; + + const incidents = await api.getIncidentsByServiceId(service.id); + const oncalls = await api.getOncallByPolicyId(service.escalation_policy.id); return { - pagerDutyServices: [ - { - activeIncidents: incidents, - escalationPolicy: oncalls, - id: service.id, - name: service.name, - homepageUrl: service.html_url, - }, - ], + incidents, + oncalls, + id: service.id, + name: service.name, + homepageUrl: service.html_url, }; }); if (error) { - // TODO: use the errorApi - console.log(error); - throw new Error(`Error in getting data: ${error.message}`); + return ( +
+ + Error encountered while fetching information. {error.message} + +
+ ); } if (loading) { return ; } - const { - activeIncidents, - escalationPolicy, - homepageUrl, - } = value!.pagerDutyServices[0]!; - const link = { title: 'View in PagerDuty', - link: homepageUrl, + link: value!.homepageUrl, }; return ( - - + + diff --git a/plugins/pagerduty/src/components/TriggerDialog.tsx b/plugins/pagerduty/src/components/TriggerDialog.tsx index 7dbb0be5f0..13c2ba6aad 100644 --- a/plugins/pagerduty/src/components/TriggerDialog.tsx +++ b/plugins/pagerduty/src/components/TriggerDialog.tsx @@ -26,7 +26,7 @@ import { } from '@material-ui/core'; import { Progress, useApi, alertApiRef, identityApiRef } from '@backstage/core'; import { useAsyncFn } from 'react-use'; -import { triggerPagerDutyAlarm } from '../api/pagerDutyClient'; +import { pagerDutyApiRef } from '../api/pagerDutyClient'; const useStyles = makeStyles({ warningText: { @@ -48,6 +48,7 @@ export const TriggerDialog = ({ name, integrationKey, onClose }: Props) => { const alertApi = useApi(alertApiRef); const identityApi = useApi(identityApiRef); const userId = identityApi.getUserId(); + const api = useApi(pagerDutyApiRef); const descriptionChanged = (event: any) => { setDescription(event.target.value); @@ -55,7 +56,7 @@ export const TriggerDialog = ({ name, integrationKey, onClose }: Props) => { const [{ value, loading, error }, triggerAlarm] = useAsyncFn( async (desc: string) => { - return await triggerPagerDutyAlarm( + return await api.triggerPagerDutyAlarm( integrationKey, window.location.toString(), desc, diff --git a/plugins/pagerduty/src/plugin.ts b/plugins/pagerduty/src/plugin.ts index 8f8523b78b..4073551dc8 100644 --- a/plugins/pagerduty/src/plugin.ts +++ b/plugins/pagerduty/src/plugin.ts @@ -13,7 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createPlugin, createRouteRef } from '@backstage/core'; +import { + createApiFactory, + createPlugin, + createRouteRef, + configApiRef, +} from '@backstage/core'; +import { pagerDutyApiRef, PagerDutyClientApi } from './api/pagerDutyClient'; export const rootRouteRef = createRouteRef({ path: '/pagerduty', @@ -22,4 +28,14 @@ export const rootRouteRef = createRouteRef({ export const plugin = createPlugin({ id: 'pagerduty', + apis: [ + createApiFactory({ + api: pagerDutyApiRef, + deps: { configApi: configApiRef }, + factory: ({ configApi }) => + new PagerDutyClientApi({ + token: configApi.getOptionalString('pagerduty.api.token'), + }), + }), + ], });