Skip to content

Commit

Permalink
feat: migrate api calls (#59)
Browse files Browse the repository at this point in the history
### Description

This PR moves the direct REST API calls from the frontend component to
the backend. Instead of leveraging the Backstage proxy to make direct
API calls to PagerDuty the calls are made now to the backend plugin.

This removes the dependency on the proxy and prevents other plugins from
using the PagerDuty proxy configuration to call PagerDuty APIs for other
purposes which raises few security concerns.

This PR also fixes an issue with the on-call users not showing up (#58).

**Issue number:** #37.

### Type of change

- [x] New feature (non-breaking change which adds functionality)
- [x] Fix (non-breaking change which fixes an issue)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected)

### Checklist

- [x] I have performed a self-review of this change
- [x] Changes have been tested
- [x] Changes are documented
- [x] Changes generate *no new warnings*
- [x] PR title follows [conventional commit
semantics](https://www.conventionalcommits.org/en/v1.0.0/)

If this is a breaking change 👇

- [ ] I have documented the migration process
- [ ] I have implemented necessary warnings (if it can live side by
side)

## Acknowledgement

By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice.

**Disclaimer:** We value your time and bandwidth. As such, any pull
requests created on non-triaged issues might not be successful.
  • Loading branch information
t1agob authored Jan 26, 2024
2 parents 7661f87 + 6fcfba8 commit 6b57f8a
Show file tree
Hide file tree
Showing 19 changed files with 164 additions and 413 deletions.
61 changes: 25 additions & 36 deletions dev/mockPagerDutyApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,23 @@
*/
import {
PagerDutyApi,
PagerDutyChangeEvent,
PagerDutyEntity,
PagerDutyIncident,
PagerDutyTriggerAlarmRequest,
} from '../src';
import { PagerDutyChangeEvent, PagerDutyIncident, PagerDutyUser } from '@pagerduty/backstage-plugin-common';
import { Entity } from '@backstage/catalog-model';

export const mockPagerDutyApi: PagerDutyApi = {
async getServiceByPagerDutyEntity(pagerDutyEntity: PagerDutyEntity) {
return {
service: {
name: pagerDutyEntity.name,
integrationKey: 'key',
id: '123',
html_url: 'http://service',
id: "SERV1CE1D",
html_url: "www.example.com",
escalation_policy: {
id: '123',
html_url: 'http://escalationpolicy',
user: {
id: '123',
summary: 'summary',
email: '[email protected]',
html_url: 'http://user',
name: 'some-user',
avatar_url: 'http://avatar',
},
id: "ESCALAT1ONP01ICY1D",
name: "ep-one",
html_url: "http://www.example.com/escalation-policy/ESCALAT1ONP01ICY1D",
},
},
};
Expand All @@ -50,20 +41,12 @@ export const mockPagerDutyApi: PagerDutyApi = {
return {
service: {
name: entity.metadata.name,
integrationKey: 'key',
id: '123',
html_url: 'http://service',
id: "SERV1CE1D",
html_url: "www.example.com",
escalation_policy: {
id: '123',
html_url: 'http://escalationpolicy',
user: {
id: '123',
summary: 'summary',
email: '[email protected]',
html_url: 'http://user',
name: 'some-user',
avatar_url: 'http://avatar',
},
id: "ESCALAT1ONP01ICY1D",
name: "ep-one",
html_url: "http://www.example.com/escalation-policy/ESCALAT1ONP01ICY1D",
},
},
};
Expand All @@ -85,12 +68,17 @@ export const mockPagerDutyApi: PagerDutyApi = {
},
},
],
serviceId: serviceId,
service: {
id: serviceId,
summary: 'service summary',
html_url: 'http://service',
},
created_at: '2015-10-06T21:30:42Z',
} as PagerDutyIncident;
};

return {

incidents: [
incident('Some Alerting Incident'),
incident('Another Alerting Incident'),
Expand Down Expand Up @@ -124,23 +112,24 @@ export const mockPagerDutyApi: PagerDutyApi = {
},

async getOnCallByPolicyId() {
const oncall = (id: string, name: string, escalation: number) => {
const oncall = (id: string, name: string) => {
return {
user: {
id: id,
name: name,
html_url: 'http://assignee',
summary: 'summary',
email: '[email protected]',
avatar_url: 'http://avatar',
},
escalation_level: escalation,
};
};

return {
oncalls: [oncall('1', 'Jane Doe', 1), oncall('2', 'John Doe', 2), oncall('3', 'James Doe', 1)],
};
const users: PagerDutyUser[] = [
oncall('1', 'Jane Doe'),
oncall('2', 'John Doe'),
oncall('3', 'James Doe'),
];

return users;
},

async triggerAlarm(request: PagerDutyTriggerAlarmRequest) {
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"react-use": "^17.2.4"
},
"peerDependencies": {
"@pagerduty/backstage-plugin-common": "^0.0.1-next.13",
"react": "^16.13.1 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0",
"react-router-dom": "6.0.0-beta.0 || ^6.3.0"
Expand All @@ -59,6 +60,7 @@
"@backstage/test-utils": "^1.4.5",
"@commitlint/cli": "^17.7.1",
"@commitlint/config-conventional": "^17.7.0",
"@pagerduty/backstage-plugin-common": "^0.0.1-next.13",
"@testing-library/dom": "^8.0.0",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^12.1.3",
Expand Down
44 changes: 17 additions & 27 deletions src/api/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import { MockFetchApi } from '@backstage/test-utils';
import { DiscoveryApi } from '@backstage/core-plugin-api';
import { PagerDutyClient, UnauthorizedError } from './client';
import { PagerDutyService, PagerDutyUser } from '../components/types';
import { PagerDutyService } from '@pagerduty/backstage-plugin-common';
import { NotFoundError } from '@backstage/errors';
import { Entity } from '@backstage/catalog-model';

Expand All @@ -25,7 +25,7 @@ const mockDiscoveryApi: jest.Mocked<DiscoveryApi> = {
getBaseUrl: jest
.fn()
.mockName('discoveryApi')
.mockResolvedValue('http://localhost:7007/proxy'),
.mockResolvedValue('http://localhost:7007/pagerduty'),
};
const mockFetchApi: MockFetchApi = new MockFetchApi({
baseImplementation: mockFetch,
Expand All @@ -34,25 +34,15 @@ const mockFetchApi: MockFetchApi = new MockFetchApi({
let client: PagerDutyClient;
let entity: Entity;

const user: PagerDutyUser = {
name: 'person1',
id: 'p1',
summary: 'person1',
email: '[email protected]',
html_url: 'http://a.com/id1',
avatar_url: 'http://a.com/id1/avatar',
};

const service: PagerDutyService = {
id: 'def456',
name: 'pagerduty-name',
html_url: 'www.example.com',
id: "SERV1CE1D",
name: "service-one",
html_url: "www.example.com",
escalation_policy: {
id: 'def',
user: user,
html_url: 'http://a.com/id1',
id: "ESCALAT1ONP01ICY1D",
name: "ep-one",
html_url: "http://www.example.com/escalation-policy/ESCALAT1ONP01ICY1D",
},
integrationKey: 'abc123',
};

const requestHeaders = {
Expand Down Expand Up @@ -93,14 +83,14 @@ describe('PagerDutyClient', () => {
mockFetch.mockResolvedValueOnce({
status: 200,
ok: true,
json: () => Promise.resolve({ services: [service] }),
json: () => Promise.resolve({ service }),
});

expect(await client.getServiceByEntity(entity)).toEqual({
service,
});
expect(mockFetch).toHaveBeenCalledWith(
'http://localhost:7007/proxy/pagerduty/services?time_zone=UTC&include[]=integrations&include[]=escalation_policies&query=abc123',
'http://localhost:7007/pagerduty/services?integration_key=abc123',
requestHeaders,
);
});
Expand Down Expand Up @@ -161,7 +151,7 @@ describe('PagerDutyClient', () => {
mockFetch.mockResolvedValueOnce({
status: 200,
ok: true,
json: () => Promise.resolve({ services: [] }),
json: () => Promise.resolve({ }),
});
});

Expand All @@ -181,7 +171,7 @@ describe('PagerDutyClient', () => {
metadata: {
name: 'pagerduty-test',
annotations: {
'pagerduty.com/service-id': 'def456',
'pagerduty.com/service-id': 'SERVICE1D',
},
},
};
Expand All @@ -198,7 +188,7 @@ describe('PagerDutyClient', () => {
service,
});
expect(mockFetch).toHaveBeenCalledWith(
'http://localhost:7007/proxy/pagerduty/services/def456?time_zone=UTC&include[]=integrations&include[]=escalation_policies',
'http://localhost:7007/pagerduty/services/SERVICE1D',
requestHeaders,
);
});
Expand Down Expand Up @@ -270,18 +260,18 @@ describe('PagerDutyClient', () => {
};
});

it('queries proxy path by integration id', async () => {
it('queries pagerduty path by integration id', async () => {
mockFetch.mockResolvedValueOnce({
status: 200,
ok: true,
json: () => Promise.resolve({ services: [service] }),
json: () => Promise.resolve({ service }),
});

expect(await client.getServiceByEntity(entity)).toEqual({
service,
});
expect(mockFetch).toHaveBeenCalledWith(
'http://localhost:7007/proxy/pagerduty/services?time_zone=UTC&include[]=integrations&include[]=escalation_policies&query=abc123',
'http://localhost:7007/pagerduty/services?integration_key=abc123',
requestHeaders,
);
});
Expand Down Expand Up @@ -342,7 +332,7 @@ describe('PagerDutyClient', () => {
mockFetch.mockResolvedValueOnce({
status: 200,
ok: true,
json: () => Promise.resolve({ services: [] }),
json: () => Promise.resolve({ }),
});
});

Expand Down
50 changes: 23 additions & 27 deletions src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@
import {
PagerDutyApi,
PagerDutyTriggerAlarmRequest,
PagerDutyServicesResponse,
PagerDutyServiceResponse,
PagerDutyIncidentsResponse,
PagerDutyOnCallsResponse,
PagerDutyClientApiDependencies,
PagerDutyClientApiConfig,
RequestOptions,
PagerDutyChangeEventsResponse,
} from './types';
import { PagerDutyChangeEventsResponse,
PagerDutyOnCallUsersResponse,
PagerDutyUser,
PagerDutyServiceResponse,
PagerDutyIncidentsResponse
} from '@pagerduty/backstage-plugin-common';
import { createApiRef, ConfigApi } from '@backstage/core-plugin-api';
import { NotFoundError } from '@backstage/errors';
import { Entity } from '@backstage/catalog-model';
Expand All @@ -40,9 +41,6 @@ export const pagerDutyApiRef = createApiRef<PagerDutyApi>({
id: 'plugin.pagerduty.api',
});

const commonGetServiceParams =
'time_zone=UTC&include[]=integrations&include[]=escalation_policies';

/** @public */
export class PagerDutyClient implements PagerDutyApi {
static fromConfig(
Expand Down Expand Up @@ -74,18 +72,17 @@ export class PagerDutyClient implements PagerDutyApi {

if (integrationKey) {
url = `${await this.config.discoveryApi.getBaseUrl(
'proxy',
)}/pagerduty/services?${commonGetServiceParams}&query=${integrationKey}`;
const { services } = await this.findByUrl<PagerDutyServicesResponse>(url);
const service = services[0];
'pagerduty',
)}/services?integration_key=${integrationKey}`;
const serviceResponse = await this.findByUrl<PagerDutyServiceResponse>(url);

if (!service) throw new NotFoundError();
if (serviceResponse.service === undefined) throw new NotFoundError();

response = { service };
response = serviceResponse;
} else if (serviceId) {
url = `${await this.config.discoveryApi.getBaseUrl(
'proxy',
)}/pagerduty/services/${serviceId}?${commonGetServiceParams}`;
'pagerduty',
)}/services/${serviceId}`;

response = await this.findByUrl<PagerDutyServiceResponse>(url);
} else {
Expand All @@ -101,34 +98,33 @@ export class PagerDutyClient implements PagerDutyApi {
async getIncidentsByServiceId(
serviceId: string,
): Promise<PagerDutyIncidentsResponse> {
const params = `time_zone=UTC&sort_by=created_at&statuses[]=triggered&statuses[]=acknowledged&service_ids[]=${serviceId}`;
const url = `${await this.config.discoveryApi.getBaseUrl(
'proxy',
)}/pagerduty/incidents?${params}`;
'pagerduty',
)}/services/${serviceId}/incidents`;

return await this.findByUrl<PagerDutyIncidentsResponse>(url);
}

async getChangeEventsByServiceId(
serviceId: string,
): Promise<PagerDutyChangeEventsResponse> {
const params = `limit=5&time_zone=UTC&sort_by=timestamp`;
const url = `${await this.config.discoveryApi.getBaseUrl(
'proxy',
)}/pagerduty/services/${serviceId}/change_events?${params}`;
'pagerduty',
)}/services/${serviceId}/change-events`;

return await this.findByUrl<PagerDutyChangeEventsResponse>(url);
}

async getOnCallByPolicyId(
policyId: string,
): Promise<PagerDutyOnCallsResponse> {
const params = `time_zone=UTC&include[]=users&escalation_policy_ids[]=${policyId}`;
): Promise<PagerDutyUser[]> {
const params = `escalation_policy_ids[]=${policyId}`;
const url = `${await this.config.discoveryApi.getBaseUrl(
'proxy',
)}/pagerduty/oncalls?${params}`;
'pagerduty',
)}/oncall-users?${params}`;

return await this.findByUrl<PagerDutyOnCallsResponse>(url);
const response: PagerDutyOnCallUsersResponse = await this.findByUrl<PagerDutyOnCallUsersResponse>(url);
return response.users;
}

triggerAlarm(request: PagerDutyTriggerAlarmRequest): Promise<Response> {
Expand Down
4 changes: 0 additions & 4 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@
export { PagerDutyClient, pagerDutyApiRef, UnauthorizedError } from './client';
export type {
PagerDutyApi,
PagerDutyServiceResponse,
PagerDutyIncidentsResponse,
PagerDutyChangeEventsResponse,
PagerDutyOnCallsResponse,
PagerDutyTriggerAlarmRequest,
PagerDutyClientApiDependencies,
PagerDutyClientApiConfig,
Expand Down
Loading

0 comments on commit 6b57f8a

Please sign in to comment.