add a homepage component to pagerduty
Signed-off-by: Brian Fletcher <brian@roadie.io>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-pagerduty': patch
|
||||
---
|
||||
|
||||
Add a homepage widget for the `Pagerduty` component.
|
||||
@@ -95,6 +95,30 @@ annotations:
|
||||
pagerduty.com/integration-key: [INTEGRATION_KEY]
|
||||
```
|
||||
|
||||
### The homepage component
|
||||
|
||||
You may also add the component to the homepage of Backstage. Edit the `HomePage.tsx` file, import `PagerDutyHomepageCard` and add it to the home page. e.g.
|
||||
|
||||
```typescript jsx
|
||||
...
|
||||
export const homePage = (
|
||||
<Page themeId="home">
|
||||
...
|
||||
<Content>
|
||||
<CustomHomepageGrid config={defaultConfig}>
|
||||
...
|
||||
<PagerDutyHomepageCard
|
||||
name="My Service"
|
||||
serviceId="<service id>"
|
||||
integrationKey="<integration key>"
|
||||
readOnly={false}
|
||||
/>
|
||||
</CustomHomepageGrid>
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
```
|
||||
|
||||
Next, provide the [API token](https://support.pagerduty.com/docs/generating-api-keys#generating-a-general-access-rest-api-key) that the client will use to make requests to the [PagerDuty API](https://developer.pagerduty.com/docs/rest-api-v2/rest-api/).
|
||||
|
||||
Add the proxy configuration in `app-config.yaml`:
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"@backstage/core-plugin-api": "workspace:^",
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@backstage/plugin-catalog-react": "workspace:^",
|
||||
"@backstage/plugin-home": "workspace:^",
|
||||
"@backstage/theme": "workspace:^",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
|
||||
@@ -30,6 +30,7 @@ import { createApiRef, ConfigApi } from '@backstage/core-plugin-api';
|
||||
import { NotFoundError } from '@backstage/errors';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { getPagerDutyEntity } from '../components/pagerDutyEntity';
|
||||
import { PagerDutyEntity } from '../types';
|
||||
|
||||
/** @public */
|
||||
export class UnauthorizedError extends Error {}
|
||||
@@ -63,8 +64,10 @@ export class PagerDutyClient implements PagerDutyApi {
|
||||
|
||||
constructor(private readonly config: PagerDutyClientApiConfig) {}
|
||||
|
||||
async getServiceByEntity(entity: Entity): Promise<PagerDutyServiceResponse> {
|
||||
const { integrationKey, serviceId } = getPagerDutyEntity(entity);
|
||||
async getServiceByPagerDutyEntity(
|
||||
pagerDutyEntity: PagerDutyEntity,
|
||||
): Promise<PagerDutyServiceResponse> {
|
||||
const { integrationKey, serviceId } = pagerDutyEntity;
|
||||
|
||||
let response: PagerDutyServiceResponse;
|
||||
let url: string;
|
||||
@@ -92,6 +95,9 @@ export class PagerDutyClient implements PagerDutyApi {
|
||||
return response;
|
||||
}
|
||||
|
||||
async getServiceByEntity(entity: Entity): Promise<PagerDutyServiceResponse> {
|
||||
return await this.getServiceByPagerDutyEntity(getPagerDutyEntity(entity));
|
||||
}
|
||||
async getIncidentsByServiceId(
|
||||
serviceId: string,
|
||||
): Promise<PagerDutyIncidentsResponse> {
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
} from '../components/types';
|
||||
import { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { PagerDutyEntity } from '../types';
|
||||
|
||||
export type PagerDutyServicesResponse = {
|
||||
services: PagerDutyService[];
|
||||
@@ -57,6 +58,14 @@ export type PagerDutyTriggerAlarmRequest = {
|
||||
|
||||
/** @public */
|
||||
export interface PagerDutyApi {
|
||||
/**
|
||||
* Fetches the service for the provided pager duty Entity.
|
||||
*
|
||||
*/
|
||||
getServiceByPagerDutyEntity(
|
||||
pagerDutyEntity: PagerDutyEntity,
|
||||
): Promise<PagerDutyServiceResponse>;
|
||||
|
||||
/**
|
||||
* Fetches the service for the provided Entity.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,386 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { render, waitFor, fireEvent, act } from '@testing-library/react';
|
||||
import {
|
||||
EntityPagerDutyCard,
|
||||
isPluginApplicableToEntity,
|
||||
} from '../EntityPagerDutyCard';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog-react';
|
||||
import { NotFoundError } from '@backstage/errors';
|
||||
import { TestApiRegistry, wrapInTestApp } from '@backstage/test-utils';
|
||||
import { pagerDutyApiRef, UnauthorizedError, PagerDutyClient } from '../../api';
|
||||
import { PagerDutyService, PagerDutyUser } from '../types';
|
||||
|
||||
import { alertApiRef } from '@backstage/core-plugin-api';
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
|
||||
const entity: Entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'pagerduty-test',
|
||||
annotations: {
|
||||
'pagerduty.com/integration-key': 'abc123',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const entityWithoutAnnotations: Entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'pagerduty-test',
|
||||
annotations: {},
|
||||
},
|
||||
};
|
||||
|
||||
const entityWithServiceId: Entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'pagerduty-test',
|
||||
annotations: {
|
||||
'pagerduty.com/service-id': 'def456',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const entityWithAllAnnotations: Entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'pagerduty-test',
|
||||
annotations: {
|
||||
'pagerduty.com/integration-key': 'abc123',
|
||||
'pagerduty.com/service-id': 'def456',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const user: PagerDutyUser = {
|
||||
name: 'person1',
|
||||
id: 'p1',
|
||||
summary: 'person1',
|
||||
email: 'person1@example.com',
|
||||
html_url: 'http://a.com/id1',
|
||||
};
|
||||
|
||||
const service: PagerDutyService = {
|
||||
id: 'def456',
|
||||
name: 'pagerduty-name',
|
||||
html_url: 'www.example.com',
|
||||
escalation_policy: {
|
||||
id: 'def',
|
||||
user: user,
|
||||
html_url: 'http://a.com/id1',
|
||||
},
|
||||
integrationKey: 'abc123',
|
||||
};
|
||||
|
||||
const mockPagerDutyApi: Partial<PagerDutyClient> = {
|
||||
getServiceByEntity: async () => ({ service }),
|
||||
getServiceByPagerDutyEntity: async () => ({ service }),
|
||||
getOnCallByPolicyId: async () => ({ oncalls: [] }),
|
||||
getIncidentsByServiceId: async () => ({ incidents: [] }),
|
||||
};
|
||||
|
||||
const apis = TestApiRegistry.from(
|
||||
[pagerDutyApiRef, mockPagerDutyApi],
|
||||
[alertApiRef, {}],
|
||||
);
|
||||
|
||||
describe('isPluginApplicableToEntity', () => {
|
||||
describe('when entity has no annotations', () => {
|
||||
it('returns false', () => {
|
||||
expect(isPluginApplicableToEntity(entityWithoutAnnotations)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when entity has the pagerduty.com/integration-key annotation', () => {
|
||||
it('returns true', () => {
|
||||
expect(isPluginApplicableToEntity(entity)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when entity has the pagerduty.com/service-id annotation', () => {
|
||||
it('returns true', () => {
|
||||
expect(isPluginApplicableToEntity(entityWithServiceId)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when entity has all annotations', () => {
|
||||
it('returns true', () => {
|
||||
expect(isPluginApplicableToEntity(entityWithAllAnnotations)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('EntityPagerDutyCard', () => {
|
||||
it('Render pagerduty', async () => {
|
||||
mockPagerDutyApi.getServiceByPagerDutyEntity = jest
|
||||
.fn()
|
||||
.mockImplementationOnce(async () => ({ service }));
|
||||
|
||||
const { getByText, queryByTestId } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entity}>
|
||||
<EntityPagerDutyCard />
|
||||
</EntityProvider>
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
await waitFor(() => !queryByTestId('progress'));
|
||||
expect(getByText('Service Directory')).toBeInTheDocument();
|
||||
expect(getByText('Create Incident')).toBeInTheDocument();
|
||||
expect(getByText('Nice! No incidents found!')).toBeInTheDocument();
|
||||
expect(getByText('Empty escalation policy')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Handles custom error for missing token', async () => {
|
||||
mockPagerDutyApi.getServiceByPagerDutyEntity = jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new UnauthorizedError());
|
||||
|
||||
const { getByText, queryByTestId } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entity}>
|
||||
<EntityPagerDutyCard />
|
||||
</EntityProvider>
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
await waitFor(() => !queryByTestId('progress'));
|
||||
expect(getByText('Missing or invalid PagerDuty Token')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Handles custom NotFoundError', async () => {
|
||||
mockPagerDutyApi.getServiceByPagerDutyEntity = jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new NotFoundError());
|
||||
|
||||
const { getByText, queryByTestId } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entity}>
|
||||
<EntityPagerDutyCard />
|
||||
</EntityProvider>
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
await waitFor(() => !queryByTestId('progress'));
|
||||
expect(getByText('PagerDuty Service Not Found')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles general error', async () => {
|
||||
mockPagerDutyApi.getServiceByPagerDutyEntity = jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new Error('An error occurred'));
|
||||
const { getByText, queryByTestId } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entity}>
|
||||
<EntityPagerDutyCard />
|
||||
</EntityProvider>
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
await waitFor(() => !queryByTestId('progress'));
|
||||
|
||||
expect(
|
||||
getByText(
|
||||
'Error encountered while fetching information. An error occurred',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('opens the dialog when trigger button is clicked', async () => {
|
||||
mockPagerDutyApi.getServiceByPagerDutyEntity = jest
|
||||
.fn()
|
||||
.mockImplementationOnce(async () => ({ service }));
|
||||
|
||||
const { getByText, queryByTestId, getByRole } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entity}>
|
||||
<EntityPagerDutyCard />
|
||||
</EntityProvider>
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
await waitFor(() => !queryByTestId('progress'));
|
||||
expect(getByText('Service Directory')).toBeInTheDocument();
|
||||
|
||||
const triggerLink = getByText('Create Incident');
|
||||
await act(async () => {
|
||||
fireEvent.click(triggerLink);
|
||||
});
|
||||
expect(getByRole('dialog')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('when entity has the pagerduty.com/service-id annotation', () => {
|
||||
it('Renders PagerDuty service information', async () => {
|
||||
mockPagerDutyApi.getServiceByPagerDutyEntity = jest
|
||||
.fn()
|
||||
.mockImplementationOnce(async () => ({ service }));
|
||||
|
||||
const { getByText, queryByTestId } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entityWithServiceId}>
|
||||
<EntityPagerDutyCard />
|
||||
</EntityProvider>
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
await waitFor(() => !queryByTestId('progress'));
|
||||
expect(getByText('Service Directory')).toBeInTheDocument();
|
||||
expect(getByText('Create Incident')).toBeInTheDocument();
|
||||
expect(getByText('Nice! No incidents found!')).toBeInTheDocument();
|
||||
expect(getByText('Empty escalation policy')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Handles custom error for missing token', async () => {
|
||||
mockPagerDutyApi.getServiceByPagerDutyEntity = jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new UnauthorizedError());
|
||||
|
||||
const { getByText, queryByTestId } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entityWithServiceId}>
|
||||
<EntityPagerDutyCard />
|
||||
</EntityProvider>
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
await waitFor(() => !queryByTestId('progress'));
|
||||
expect(
|
||||
getByText('Missing or invalid PagerDuty Token'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Handles custom NotFoundError', async () => {
|
||||
mockPagerDutyApi.getServiceByPagerDutyEntity = jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new NotFoundError());
|
||||
|
||||
const { getByText, queryByTestId } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entityWithServiceId}>
|
||||
<EntityPagerDutyCard />
|
||||
</EntityProvider>
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
await waitFor(() => !queryByTestId('progress'));
|
||||
expect(getByText('PagerDuty Service Not Found')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles general error', async () => {
|
||||
mockPagerDutyApi.getServiceByPagerDutyEntity = jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new Error('An error occurred'));
|
||||
const { getByText, queryByTestId } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entityWithServiceId}>
|
||||
<EntityPagerDutyCard />
|
||||
</EntityProvider>
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
await waitFor(() => !queryByTestId('progress'));
|
||||
|
||||
expect(
|
||||
getByText(
|
||||
'Error encountered while fetching information. An error occurred',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('disables the Create Incident button', async () => {
|
||||
mockPagerDutyApi.getServiceByPagerDutyEntity = jest
|
||||
.fn()
|
||||
.mockImplementationOnce(async () => ({ service }));
|
||||
|
||||
const { queryByTestId, getByTitle } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entityWithServiceId}>
|
||||
<EntityPagerDutyCard />
|
||||
</EntityProvider>
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
await waitFor(() => !queryByTestId('progress'));
|
||||
expect(
|
||||
getByTitle('Must provide an integration-key to create incidents')
|
||||
.className,
|
||||
).toMatch('disabled');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when entity has all annotations', () => {
|
||||
it('queries by integration key', async () => {
|
||||
mockPagerDutyApi.getServiceByPagerDutyEntity = jest
|
||||
.fn()
|
||||
.mockImplementationOnce(async () => ({ service }));
|
||||
|
||||
const { getByText, queryByTestId } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entityWithAllAnnotations}>
|
||||
<EntityPagerDutyCard />
|
||||
</EntityProvider>
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
await waitFor(() => !queryByTestId('progress'));
|
||||
expect(getByText('Service Directory')).toBeInTheDocument();
|
||||
expect(getByText('Create Incident')).toBeInTheDocument();
|
||||
expect(getByText('Nice! No incidents found!')).toBeInTheDocument();
|
||||
expect(getByText('Empty escalation policy')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when entity has all annotations but the plugin has been configured to be "read only"', () => {
|
||||
it('queries by integration key but does not render the "Create Incident" button', async () => {
|
||||
mockPagerDutyApi.getServiceByPagerDutyEntity = jest
|
||||
.fn()
|
||||
.mockImplementationOnce(async () => ({ service }));
|
||||
|
||||
const { getByText, queryByTestId } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entityWithAllAnnotations}>
|
||||
<EntityPagerDutyCard readOnly />
|
||||
</EntityProvider>
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
await waitFor(() => !queryByTestId('progress'));
|
||||
expect(getByText('Service Directory')).toBeInTheDocument();
|
||||
expect(getByText('Nice! No incidents found!')).toBeInTheDocument();
|
||||
expect(getByText('Empty escalation policy')).toBeInTheDocument();
|
||||
expect(() => getByText('Create Incident')).toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { PAGERDUTY_INTEGRATION_KEY, PAGERDUTY_SERVICE_ID } from '../constants';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import { getPagerDutyEntity } from '../pagerDutyEntity';
|
||||
import { PagerDutyCard } from '../PagerDutyCard';
|
||||
|
||||
/** @public */
|
||||
export const isPluginApplicableToEntity = (entity: Entity) =>
|
||||
Boolean(
|
||||
entity.metadata.annotations?.[PAGERDUTY_INTEGRATION_KEY] ||
|
||||
entity.metadata.annotations?.[PAGERDUTY_SERVICE_ID],
|
||||
);
|
||||
|
||||
/** @public */
|
||||
export type PagerDutyCardProps = {
|
||||
readOnly?: boolean;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export const EntityPagerDutyCard = (props: PagerDutyCardProps) => {
|
||||
const { readOnly } = props;
|
||||
const { entity } = useEntity();
|
||||
const pagerDutyEntity = getPagerDutyEntity(entity);
|
||||
return <PagerDutyCard {...pagerDutyEntity} readOnly={readOnly} />;
|
||||
};
|
||||
@@ -21,7 +21,7 @@ export const ServiceNotFoundError = () => (
|
||||
<EmptyState
|
||||
missing="data"
|
||||
title="PagerDuty Service Not Found"
|
||||
description="A service could not be found within PagerDuty based on the provided annotations. Please verify your annotation details."
|
||||
description="A service could not be found within PagerDuty based on the provided service id. Please verify your configuration."
|
||||
action={
|
||||
<Button
|
||||
color="primary"
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import { render, waitFor, fireEvent, act } from '@testing-library/react';
|
||||
import { PagerDutyCard, isPluginApplicableToEntity } from '../PagerDutyCard';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog-react';
|
||||
import { PagerDutyCard } from '../PagerDutyCard';
|
||||
import { NotFoundError } from '@backstage/errors';
|
||||
import { TestApiRegistry, wrapInTestApp } from '@backstage/test-utils';
|
||||
import { pagerDutyApiRef, UnauthorizedError, PagerDutyClient } from '../../api';
|
||||
@@ -26,49 +24,6 @@ import { PagerDutyService, PagerDutyUser } from '../types';
|
||||
import { alertApiRef } from '@backstage/core-plugin-api';
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
|
||||
const entity: Entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'pagerduty-test',
|
||||
annotations: {
|
||||
'pagerduty.com/integration-key': 'abc123',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const entityWithoutAnnotations: Entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'pagerduty-test',
|
||||
annotations: {},
|
||||
},
|
||||
};
|
||||
|
||||
const entityWithServiceId: Entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'pagerduty-test',
|
||||
annotations: {
|
||||
'pagerduty.com/service-id': 'def456',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const entityWithAllAnnotations: Entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'pagerduty-test',
|
||||
annotations: {
|
||||
'pagerduty.com/integration-key': 'abc123',
|
||||
'pagerduty.com/service-id': 'def456',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const user: PagerDutyUser = {
|
||||
name: 'person1',
|
||||
id: 'p1',
|
||||
@@ -100,44 +55,16 @@ const apis = TestApiRegistry.from(
|
||||
[alertApiRef, {}],
|
||||
);
|
||||
|
||||
describe('isPluginApplicableToEntity', () => {
|
||||
describe('when entity has no annotations', () => {
|
||||
it('returns false', () => {
|
||||
expect(isPluginApplicableToEntity(entityWithoutAnnotations)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when entity has the pagerduty.com/integration-key annotation', () => {
|
||||
it('returns true', () => {
|
||||
expect(isPluginApplicableToEntity(entity)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when entity has the pagerduty.com/service-id annotation', () => {
|
||||
it('returns true', () => {
|
||||
expect(isPluginApplicableToEntity(entityWithServiceId)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when entity has all annotations', () => {
|
||||
it('returns true', () => {
|
||||
expect(isPluginApplicableToEntity(entityWithAllAnnotations)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('PageDutyCard', () => {
|
||||
describe('PagerDutyCard', () => {
|
||||
it('Render pagerduty', async () => {
|
||||
mockPagerDutyApi.getServiceByEntity = jest
|
||||
mockPagerDutyApi.getServiceByPagerDutyEntity = jest
|
||||
.fn()
|
||||
.mockImplementationOnce(async () => ({ service }));
|
||||
|
||||
const { getByText, queryByTestId } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entity}>
|
||||
<PagerDutyCard />
|
||||
</EntityProvider>
|
||||
<PagerDutyCard name="blah" integrationKey="abc123" />
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
@@ -149,16 +76,14 @@ describe('PageDutyCard', () => {
|
||||
});
|
||||
|
||||
it('Handles custom error for missing token', async () => {
|
||||
mockPagerDutyApi.getServiceByEntity = jest
|
||||
mockPagerDutyApi.getServiceByPagerDutyEntity = jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new UnauthorizedError());
|
||||
|
||||
const { getByText, queryByTestId } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entity}>
|
||||
<PagerDutyCard />
|
||||
</EntityProvider>
|
||||
<PagerDutyCard name="blah" integrationKey="abc123" />
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
@@ -167,16 +92,14 @@ describe('PageDutyCard', () => {
|
||||
});
|
||||
|
||||
it('Handles custom NotFoundError', async () => {
|
||||
mockPagerDutyApi.getServiceByEntity = jest
|
||||
mockPagerDutyApi.getServiceByPagerDutyEntity = jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new NotFoundError());
|
||||
|
||||
const { getByText, queryByTestId } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entity}>
|
||||
<PagerDutyCard />
|
||||
</EntityProvider>
|
||||
<PagerDutyCard name="blah" integrationKey="abc123" />
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
@@ -185,15 +108,13 @@ describe('PageDutyCard', () => {
|
||||
});
|
||||
|
||||
it('handles general error', async () => {
|
||||
mockPagerDutyApi.getServiceByEntity = jest
|
||||
mockPagerDutyApi.getServiceByPagerDutyEntity = jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new Error('An error occurred'));
|
||||
const { getByText, queryByTestId } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entity}>
|
||||
<PagerDutyCard />
|
||||
</EntityProvider>
|
||||
<PagerDutyCard name="blah" integrationKey="abc123" />
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
@@ -207,16 +128,14 @@ describe('PageDutyCard', () => {
|
||||
});
|
||||
|
||||
it('opens the dialog when trigger button is clicked', async () => {
|
||||
mockPagerDutyApi.getServiceByEntity = jest
|
||||
mockPagerDutyApi.getServiceByPagerDutyEntity = jest
|
||||
.fn()
|
||||
.mockImplementationOnce(async () => ({ service }));
|
||||
|
||||
const { getByText, queryByTestId, getByRole } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entity}>
|
||||
<PagerDutyCard />
|
||||
</EntityProvider>
|
||||
<PagerDutyCard name="blah" integrationKey="abc123" />
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
@@ -232,16 +151,14 @@ describe('PageDutyCard', () => {
|
||||
|
||||
describe('when entity has the pagerduty.com/service-id annotation', () => {
|
||||
it('Renders PagerDuty service information', async () => {
|
||||
mockPagerDutyApi.getServiceByEntity = jest
|
||||
mockPagerDutyApi.getServiceByPagerDutyEntity = jest
|
||||
.fn()
|
||||
.mockImplementationOnce(async () => ({ service }));
|
||||
|
||||
const { getByText, queryByTestId } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entityWithServiceId}>
|
||||
<PagerDutyCard />
|
||||
</EntityProvider>
|
||||
<PagerDutyCard name="blah" integrationKey="abc123" />
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
@@ -253,16 +170,18 @@ describe('PageDutyCard', () => {
|
||||
});
|
||||
|
||||
it('Handles custom error for missing token', async () => {
|
||||
mockPagerDutyApi.getServiceByEntity = jest
|
||||
mockPagerDutyApi.getServiceByPagerDutyEntity = jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new UnauthorizedError());
|
||||
|
||||
const { getByText, queryByTestId } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entityWithServiceId}>
|
||||
<PagerDutyCard />
|
||||
</EntityProvider>
|
||||
<PagerDutyCard
|
||||
name="blah"
|
||||
integrationKey="abc123"
|
||||
serviceId="def123"
|
||||
/>
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
@@ -273,16 +192,18 @@ describe('PageDutyCard', () => {
|
||||
});
|
||||
|
||||
it('Handles custom NotFoundError', async () => {
|
||||
mockPagerDutyApi.getServiceByEntity = jest
|
||||
mockPagerDutyApi.getServiceByPagerDutyEntity = jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new NotFoundError());
|
||||
|
||||
const { getByText, queryByTestId } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entityWithServiceId}>
|
||||
<PagerDutyCard />
|
||||
</EntityProvider>
|
||||
<PagerDutyCard
|
||||
name="blah"
|
||||
integrationKey="abc123"
|
||||
serviceId="def123"
|
||||
/>
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
@@ -291,15 +212,17 @@ describe('PageDutyCard', () => {
|
||||
});
|
||||
|
||||
it('handles general error', async () => {
|
||||
mockPagerDutyApi.getServiceByEntity = jest
|
||||
mockPagerDutyApi.getServiceByPagerDutyEntity = jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new Error('An error occurred'));
|
||||
const { getByText, queryByTestId } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entityWithServiceId}>
|
||||
<PagerDutyCard />
|
||||
</EntityProvider>
|
||||
<PagerDutyCard
|
||||
name="blah"
|
||||
integrationKey="abc123"
|
||||
serviceId="def123"
|
||||
/>
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
@@ -313,16 +236,14 @@ describe('PageDutyCard', () => {
|
||||
});
|
||||
|
||||
it('disables the Create Incident button', async () => {
|
||||
mockPagerDutyApi.getServiceByEntity = jest
|
||||
mockPagerDutyApi.getServiceByPagerDutyEntity = jest
|
||||
.fn()
|
||||
.mockImplementationOnce(async () => ({ service }));
|
||||
|
||||
const { queryByTestId, getByTitle } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entityWithServiceId}>
|
||||
<PagerDutyCard />
|
||||
</EntityProvider>
|
||||
<PagerDutyCard name="blah" serviceId="def123" />
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
@@ -336,16 +257,18 @@ describe('PageDutyCard', () => {
|
||||
|
||||
describe('when entity has all annotations', () => {
|
||||
it('queries by integration key', async () => {
|
||||
mockPagerDutyApi.getServiceByEntity = jest
|
||||
mockPagerDutyApi.getServiceByPagerDutyEntity = jest
|
||||
.fn()
|
||||
.mockImplementationOnce(async () => ({ service }));
|
||||
|
||||
const { getByText, queryByTestId } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entityWithAllAnnotations}>
|
||||
<PagerDutyCard />
|
||||
</EntityProvider>
|
||||
<PagerDutyCard
|
||||
name="blah"
|
||||
integrationKey="abc123"
|
||||
serviceId="def123"
|
||||
/>
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
@@ -359,16 +282,19 @@ describe('PageDutyCard', () => {
|
||||
|
||||
describe('when entity has all annotations but the plugin has been configured to be "read only"', () => {
|
||||
it('queries by integration key but does not render the "Create Incident" button', async () => {
|
||||
mockPagerDutyApi.getServiceByEntity = jest
|
||||
mockPagerDutyApi.getServiceByPagerDutyEntity = jest
|
||||
.fn()
|
||||
.mockImplementationOnce(async () => ({ service }));
|
||||
|
||||
const { getByText, queryByTestId } = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entityWithAllAnnotations}>
|
||||
<PagerDutyCard readOnly />
|
||||
</EntityProvider>
|
||||
<PagerDutyCard
|
||||
name="blah"
|
||||
integrationKey="abc123"
|
||||
serviceId="def123"
|
||||
readOnly
|
||||
/>
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React, { ReactNode, useState, useCallback } from 'react';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { Card, CardHeader, Divider, CardContent } from '@material-ui/core';
|
||||
import { Incidents } from '../Incident';
|
||||
import { EscalationPolicy } from '../Escalation';
|
||||
@@ -25,7 +24,6 @@ import AlarmAddIcon from '@material-ui/icons/AlarmAdd';
|
||||
import { MissingTokenError, ServiceNotFoundError } from '../Errors';
|
||||
import WebIcon from '@material-ui/icons/Web';
|
||||
import DateRangeIcon from '@material-ui/icons/DateRange';
|
||||
import { PAGERDUTY_INTEGRATION_KEY, PAGERDUTY_SERVICE_ID } from '../constants';
|
||||
import { TriggerDialog } from '../TriggerDialog';
|
||||
import { ChangeEvents } from '../ChangeEvents';
|
||||
|
||||
@@ -39,30 +37,20 @@ import {
|
||||
CardTab,
|
||||
InfoCard,
|
||||
} from '@backstage/core-components';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import { getPagerDutyEntity } from '../pagerDutyEntity';
|
||||
import { PagerDutyEntity } from '../../types';
|
||||
|
||||
const BasicCard = ({ children }: { children: ReactNode }) => (
|
||||
<InfoCard title="PagerDuty">{children}</InfoCard>
|
||||
);
|
||||
|
||||
/** @public */
|
||||
export const isPluginApplicableToEntity = (entity: Entity) =>
|
||||
Boolean(
|
||||
entity.metadata.annotations?.[PAGERDUTY_INTEGRATION_KEY] ||
|
||||
entity.metadata.annotations?.[PAGERDUTY_SERVICE_ID],
|
||||
);
|
||||
|
||||
/** @public */
|
||||
export type PagerDutyCardProps = {
|
||||
export type PagerDutyCardProps = PagerDutyEntity & {
|
||||
readOnly?: boolean;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export const PagerDutyCard = (props: PagerDutyCardProps) => {
|
||||
const { readOnly } = props;
|
||||
const { entity } = useEntity();
|
||||
const pagerDutyEntity = getPagerDutyEntity(entity);
|
||||
const { readOnly, integrationKey, name } = props;
|
||||
const api = useApi(pagerDutyApiRef);
|
||||
const [refreshIncidents, setRefreshIncidents] = useState<boolean>(false);
|
||||
const [refreshChangeEvents, setRefreshChangeEvents] =
|
||||
@@ -86,7 +74,9 @@ export const PagerDutyCard = (props: PagerDutyCardProps) => {
|
||||
loading,
|
||||
error,
|
||||
} = useAsync(async () => {
|
||||
const { service: foundService } = await api.getServiceByEntity(entity);
|
||||
const { service: foundService } = await api.getServiceByPagerDutyEntity(
|
||||
props,
|
||||
);
|
||||
|
||||
return {
|
||||
id: foundService.id,
|
||||
@@ -137,7 +127,7 @@ export const PagerDutyCard = (props: PagerDutyCardProps) => {
|
||||
* There is no guarantee the current user entity has a valid email association, so instead just
|
||||
* only allow triggering incidents when an integration key is present.
|
||||
*/
|
||||
const createIncidentDisabled = !pagerDutyEntity.integrationKey;
|
||||
const createIncidentDisabled = !integrationKey;
|
||||
const triggerLink: IconLinkVerticalProps = {
|
||||
label: 'Create Incident',
|
||||
onClick: showDialog,
|
||||
@@ -195,6 +185,8 @@ export const PagerDutyCard = (props: PagerDutyCardProps) => {
|
||||
showDialog={dialogShown}
|
||||
handleDialog={hideDialog}
|
||||
onIncidentCreated={handleRefresh}
|
||||
name={name}
|
||||
integrationKey={integrationKey}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
import React from 'react';
|
||||
|
||||
import { PagerDutyEntity } from '../../types';
|
||||
import { PagerDutyCard } from '../PagerDutyCard';
|
||||
|
||||
/** @public */
|
||||
export type PagerDutyCardProps = PagerDutyEntity & {
|
||||
readOnly?: boolean;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export const Content = (props: PagerDutyCardProps) => {
|
||||
return <PagerDutyCard {...props} />;
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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 { Content } from './Content';
|
||||
@@ -17,8 +17,6 @@ import React from 'react';
|
||||
import { fireEvent, act } from '@testing-library/react';
|
||||
import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
|
||||
import { pagerDutyApiRef } from '../../api';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog-react';
|
||||
import { TriggerDialog } from './TriggerDialog';
|
||||
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
@@ -49,26 +47,15 @@ describe('TriggerDialog', () => {
|
||||
);
|
||||
|
||||
it('open the dialog and trigger an alarm', async () => {
|
||||
const entity: Entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'pagerduty-test',
|
||||
annotations: {
|
||||
'pagerduty.com/integration-key': 'abc123',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const { getByText, getByRole, getByTestId } = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entity}>
|
||||
<TriggerDialog
|
||||
showDialog
|
||||
handleDialog={() => {}}
|
||||
onIncidentCreated={() => {}}
|
||||
/>
|
||||
</EntityProvider>
|
||||
<TriggerDialog
|
||||
integrationKey="abc123"
|
||||
name="pagerduty-test"
|
||||
showDialog
|
||||
handleDialog={() => {}}
|
||||
onIncidentCreated={() => {}}
|
||||
/>
|
||||
</ApiProvider>,
|
||||
);
|
||||
|
||||
@@ -89,8 +76,7 @@ describe('TriggerDialog', () => {
|
||||
});
|
||||
expect(mockTriggerAlarmFn).toHaveBeenCalled();
|
||||
expect(mockTriggerAlarmFn).toHaveBeenCalledWith({
|
||||
integrationKey:
|
||||
entity!.metadata!.annotations!['pagerduty.com/integration-key'],
|
||||
integrationKey: 'abc123',
|
||||
source: window.location.toString(),
|
||||
description,
|
||||
userName: 'guest',
|
||||
|
||||
@@ -28,7 +28,6 @@ import {
|
||||
import useAsyncFn from 'react-use/lib/useAsyncFn';
|
||||
import { pagerDutyApiRef } from '../../api';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import { usePagerdutyEntity } from '../../hooks';
|
||||
import {
|
||||
useApi,
|
||||
alertApiRef,
|
||||
@@ -40,14 +39,17 @@ type Props = {
|
||||
showDialog: boolean;
|
||||
handleDialog: () => void;
|
||||
onIncidentCreated?: () => void;
|
||||
name: string;
|
||||
integrationKey: string;
|
||||
};
|
||||
|
||||
export const TriggerDialog = ({
|
||||
showDialog,
|
||||
handleDialog,
|
||||
onIncidentCreated: onIncidentCreated,
|
||||
name,
|
||||
integrationKey,
|
||||
}: Props) => {
|
||||
const { name, integrationKey } = usePagerdutyEntity();
|
||||
const alertApi = useApi(alertApiRef);
|
||||
const identityApi = useApi(identityApiRef);
|
||||
const api = useApi(pagerDutyApiRef);
|
||||
|
||||
@@ -25,10 +25,12 @@ export type {
|
||||
|
||||
export type { PagerDutyCardProps } from './PagerDutyCard';
|
||||
|
||||
export { PagerDutyCard } from './PagerDutyCard';
|
||||
|
||||
export {
|
||||
isPluginApplicableToEntity,
|
||||
isPluginApplicableToEntity as isPagerDutyAvailable,
|
||||
PagerDutyCard,
|
||||
} from './PagerDutyCard';
|
||||
EntityPagerDutyCard,
|
||||
} from './EntityPagerDutyCard';
|
||||
|
||||
export { TriggerButton } from './TriggerButton';
|
||||
|
||||
@@ -24,6 +24,7 @@ export {
|
||||
pagerDutyPlugin,
|
||||
pagerDutyPlugin as plugin,
|
||||
EntityPagerDutyCard,
|
||||
PagerDutyHomepageCard,
|
||||
} from './plugin';
|
||||
|
||||
export * from './components';
|
||||
|
||||
@@ -23,6 +23,8 @@ import {
|
||||
configApiRef,
|
||||
createComponentExtension,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { createCardExtension } from '@backstage/plugin-home';
|
||||
import { PagerDutyCardProps } from './components/PagerDutyHomepageCard/Content';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
id: 'pagerduty',
|
||||
@@ -51,7 +53,25 @@ export const EntityPagerDutyCard = pagerDutyPlugin.provide(
|
||||
name: 'EntityPagerDutyCard',
|
||||
component: {
|
||||
lazy: () =>
|
||||
import('./components/PagerDutyCard').then(m => m.PagerDutyCard),
|
||||
import('./components/EntityPagerDutyCard').then(
|
||||
m => m.EntityPagerDutyCard,
|
||||
),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export const homePlugin = createPlugin({
|
||||
id: 'pagerduty-home',
|
||||
routes: {
|
||||
root: rootRouteRef,
|
||||
},
|
||||
});
|
||||
|
||||
/** @public */
|
||||
export const PagerDutyHomepageCard = homePlugin.provide(
|
||||
createCardExtension<PagerDutyCardProps>({
|
||||
name: 'PagerDutyHomepageCard',
|
||||
title: 'Pager Duty Homepage Card',
|
||||
components: () => import('./components/PagerDutyHomepageCard'),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -7681,6 +7681,7 @@ __metadata:
|
||||
"@backstage/dev-utils": "workspace:^"
|
||||
"@backstage/errors": "workspace:^"
|
||||
"@backstage/plugin-catalog-react": "workspace:^"
|
||||
"@backstage/plugin-home": "workspace:^"
|
||||
"@backstage/test-utils": "workspace:^"
|
||||
"@backstage/theme": "workspace:^"
|
||||
"@material-ui/core": ^4.12.2
|
||||
|
||||
Reference in New Issue
Block a user