add utility api and use it in pluign

This commit is contained in:
Samira Mokaram
2020-11-05 11:17:19 +01:00
parent 122410f1ab
commit f7614ee6fc
5 changed files with 149 additions and 122 deletions
+101 -77
View File
@@ -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<PagerDutyClientApi>({
id: 'plugin.pagerduty.api',
description: 'Used to fetch data from PagerDuty API',
});
const request = async (
url: string,
options: any, //Options,
): Promise<Response | Error> => {
const response = await fetch(url, options);
interface PagerDutyClient {
getServiceByIntegrationKey(integrationKey: string): Promise<Service[]>;
getIncidentsByServiceId(serviceId: string): Promise<Incident[]>;
getOncallByPolicyId(policyId: string): Promise<Oncall[]>;
}
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<Service[]> {
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<ServicesResponse>(url);
return services;
}
return await response.json();
};
async getIncidentsByServiceId(serviceId: string): Promise<Incident[]> {
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<IncidentResponse>(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<Oncall[]> {
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<OncallsResponse>(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<T>(url: string): Promise<T> {
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<Response> {
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);
}
}
}
@@ -114,7 +114,9 @@ type IncidentsProps = {
export const Incidents = ({ incidents }: IncidentsProps) => (
<List dense subheader={<ListSubheader>Incidents</ListSubheader>}>
{incidents.length ? (
incidents.map(incident => <IncidentListItem incident={incident} />)
incidents.map((incident, index) => (
<IncidentListItem key={incident.id + index} incident={incident} />
))
) : (
<IncidentsEmptyState />
)}
@@ -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 (
<div>
<Alert severity="error">
Error encountered while fetching information. {error.message}
</Alert>
</div>
);
}
if (loading) {
return <LinearProgress />;
}
const {
activeIncidents,
escalationPolicy,
homepageUrl,
} = value!.pagerDutyServices[0]!;
const link = {
title: 'View in PagerDuty',
link: homepageUrl,
link: value!.homepageUrl,
};
return (
<InfoCard title="PagerDuty" deepLink={link}>
<Incidents incidents={activeIncidents.incidents} />
<EscalationPolicy escalation={escalationPolicy.oncalls} />
<Incidents incidents={value!.incidents} />
<EscalationPolicy escalation={value!.oncalls} />
<Grid container item xs={12} justify="flex-end">
<TriggerButton entity={entity} />
</Grid>
@@ -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,
+17 -1
View File
@@ -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'),
}),
}),
],
});