add utility api and use it in pluign
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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'),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user