From 5539625f645704e97fabc72eadc9fcf5094cf6b4 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Mon, 19 Oct 2020 14:15:46 +0200
Subject: [PATCH 01/52] create pagerduty plugin
---
packages/app/package.json | 1 +
packages/app/src/plugins.ts | 1 +
plugins/pagerduty/.eslintrc.js | 3 ++
plugins/pagerduty/README.md | 13 ++++++++
plugins/pagerduty/dev/index.tsx | 19 ++++++++++++
plugins/pagerduty/package.json | 46 ++++++++++++++++++++++++++++
plugins/pagerduty/src/index.ts | 16 ++++++++++
plugins/pagerduty/src/plugin.test.ts | 22 +++++++++++++
plugins/pagerduty/src/plugin.ts | 25 +++++++++++++++
plugins/pagerduty/src/setupTests.ts | 18 +++++++++++
10 files changed, 164 insertions(+)
create mode 100644 plugins/pagerduty/.eslintrc.js
create mode 100644 plugins/pagerduty/README.md
create mode 100644 plugins/pagerduty/dev/index.tsx
create mode 100644 plugins/pagerduty/package.json
create mode 100644 plugins/pagerduty/src/index.ts
create mode 100644 plugins/pagerduty/src/plugin.test.ts
create mode 100644 plugins/pagerduty/src/plugin.ts
create mode 100644 plugins/pagerduty/src/setupTests.ts
diff --git a/packages/app/package.json b/packages/app/package.json
index 65ba6140b2..487e4ad0c5 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -21,6 +21,7 @@
"@backstage/plugin-kubernetes": "^0.1.1-alpha.25",
"@backstage/plugin-lighthouse": "^0.1.1-alpha.25",
"@backstage/plugin-newrelic": "^0.1.1-alpha.25",
+ "@backstage/plugin-pagerduty": "^0.1.1-alpha.25",
"@backstage/plugin-register-component": "^0.1.1-alpha.25",
"@backstage/plugin-rollbar": "^0.1.1-alpha.25",
"@backstage/plugin-scaffolder": "^0.1.1-alpha.25",
diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts
index bdc3851318..3851556882 100644
--- a/packages/app/src/plugins.ts
+++ b/packages/app/src/plugins.ts
@@ -38,3 +38,4 @@ export { plugin as Cloudbuild } from '@backstage/plugin-cloudbuild';
export { plugin as CostInsights } from '@backstage/plugin-cost-insights';
export { plugin as GitHubInsights } from '@roadiehq/backstage-plugin-github-insights';
export { plugin as UserSettings } from '@backstage/plugin-user-settings';
+export { plugin as Pagerduty } from '@backstage/plugin-pagerduty';
diff --git a/plugins/pagerduty/.eslintrc.js b/plugins/pagerduty/.eslintrc.js
new file mode 100644
index 0000000000..13573efa9c
--- /dev/null
+++ b/plugins/pagerduty/.eslintrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+ extends: [require.resolve('@backstage/cli/config/eslint')],
+};
diff --git a/plugins/pagerduty/README.md b/plugins/pagerduty/README.md
new file mode 100644
index 0000000000..0335cb5a9b
--- /dev/null
+++ b/plugins/pagerduty/README.md
@@ -0,0 +1,13 @@
+# pagerduty
+
+Welcome to the pagerduty plugin!
+
+_This plugin was created through the Backstage CLI_
+
+## Getting started
+
+Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/pagerduty](http://localhost:3000/pagerduty).
+
+You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
+This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
+It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory.
diff --git a/plugins/pagerduty/dev/index.tsx b/plugins/pagerduty/dev/index.tsx
new file mode 100644
index 0000000000..264d6f801f
--- /dev/null
+++ b/plugins/pagerduty/dev/index.tsx
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { createDevApp } from '@backstage/dev-utils';
+import { plugin } from '../src/plugin';
+
+createDevApp().registerPlugin(plugin).render();
diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json
new file mode 100644
index 0000000000..33054c6163
--- /dev/null
+++ b/plugins/pagerduty/package.json
@@ -0,0 +1,46 @@
+{
+ "name": "@backstage/plugin-pagerduty",
+ "version": "0.1.1-alpha.25",
+ "main": "src/index.ts",
+ "types": "src/index.ts",
+ "license": "Apache-2.0",
+ "publishConfig": {
+ "access": "public",
+ "main": "dist/index.esm.js",
+ "types": "dist/index.d.ts"
+ },
+ "scripts": {
+ "build": "backstage-cli plugin:build",
+ "start": "backstage-cli plugin:serve",
+ "lint": "backstage-cli lint",
+ "test": "backstage-cli test",
+ "diff": "backstage-cli plugin:diff",
+ "prepack": "backstage-cli prepack",
+ "postpack": "backstage-cli postpack",
+ "clean": "backstage-cli clean"
+ },
+ "dependencies": {
+ "@backstage/core": "^0.1.1-alpha.25",
+ "@backstage/theme": "^0.1.1-alpha.25",
+ "@material-ui/core": "^4.11.0",
+ "@material-ui/icons": "^4.9.1",
+ "@material-ui/lab": "4.0.0-alpha.45",
+ "react": "^16.13.1",
+ "react-dom": "^16.13.1",
+ "react-use": "^15.3.3"
+ },
+ "devDependencies": {
+ "@backstage/cli": "^0.1.1-alpha.25",
+ "@backstage/dev-utils": "^0.1.1-alpha.25",
+ "@testing-library/jest-dom": "^5.10.1",
+ "@testing-library/react": "^10.4.1",
+ "@testing-library/user-event": "^12.0.7",
+ "@types/jest": "^26.0.7",
+ "@types/node": "^12.0.0",
+ "msw": "^0.20.5",
+ "node-fetch": "^2.6.1"
+ },
+ "files": [
+ "dist"
+ ]
+}
diff --git a/plugins/pagerduty/src/index.ts b/plugins/pagerduty/src/index.ts
new file mode 100644
index 0000000000..224e293890
--- /dev/null
+++ b/plugins/pagerduty/src/index.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { plugin } from './plugin';
diff --git a/plugins/pagerduty/src/plugin.test.ts b/plugins/pagerduty/src/plugin.test.ts
new file mode 100644
index 0000000000..8d4545ac12
--- /dev/null
+++ b/plugins/pagerduty/src/plugin.test.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { plugin } from './plugin';
+
+describe('pagerduty', () => {
+ it('should export plugin', () => {
+ expect(plugin).toBeDefined();
+ });
+});
diff --git a/plugins/pagerduty/src/plugin.ts b/plugins/pagerduty/src/plugin.ts
new file mode 100644
index 0000000000..8f8523b78b
--- /dev/null
+++ b/plugins/pagerduty/src/plugin.ts
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { createPlugin, createRouteRef } from '@backstage/core';
+
+export const rootRouteRef = createRouteRef({
+ path: '/pagerduty',
+ title: 'pagerduty',
+});
+
+export const plugin = createPlugin({
+ id: 'pagerduty',
+});
diff --git a/plugins/pagerduty/src/setupTests.ts b/plugins/pagerduty/src/setupTests.ts
new file mode 100644
index 0000000000..d7857386de
--- /dev/null
+++ b/plugins/pagerduty/src/setupTests.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 '@testing-library/jest-dom';
+
+global.fetch = require('node-fetch');
From 47e9c6d56bd8fac1cb3d06cfefeb31e5646a9235 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Tue, 20 Oct 2020 09:55:32 +0200
Subject: [PATCH 02/52] add main functionality
---
.../src/components/PagerDutyServiceCard.tsx | 294 ++++++++++++++++++
plugins/pagerduty/src/components/pd.svg | 13 +
plugins/pagerduty/src/index.ts | 4 +
3 files changed, 311 insertions(+)
create mode 100644 plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
create mode 100644 plugins/pagerduty/src/components/pd.svg
diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
new file mode 100644
index 0000000000..3dadacadfb
--- /dev/null
+++ b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 {
+ InfoCard,
+ StatusError,
+ StatusWarning,
+ StatusOK,
+} from '@backstage/core';
+import { Entity } from '@backstage/catalog-model';
+import {
+ List,
+ ListSubheader,
+ ListItem,
+ ListItemIcon,
+ ListItemSecondaryAction,
+ Tooltip,
+ ListItemText,
+ makeStyles,
+ IconButton,
+} from '@material-ui/core';
+import moment from 'moment';
+import Pagerduty from './pd.svg';
+import UserIcon from '@material-ui/icons/Person';
+import EmailIcon from '@material-ui/icons/Email';
+
+const useStyles = makeStyles({
+ buttonContainer: {},
+ denseListIcon: {
+ marginRight: 0,
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ svgButtonImage: {
+ height: '1em',
+ },
+});
+
+type IncidentListProps = {
+ incidents: any;
+};
+
+const IncidentList = ({ incidents }: IncidentListProps) => {
+ const classes = useStyles();
+ return incidents.map((incident: any) => (
+
+
+
+
+ {incident.status === 'triggered' ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ Created {moment(incident.createdAt).fromNow()}, assigned to{' '}
+ {(incident.assignees.length && incident.assignees[0].name) ||
+ 'nobody'}
+
+ }
+ />
+
+
+
+
+
+
+
+
+ ));
+};
+
+const IncidentsEmptyState = () => {
+ const classes = useStyles();
+ return (
+
+
+
+
+
+
+
+
+ );
+};
+
+type IncidentsProps = {
+ incidents: Array;
+};
+export const Incidents = ({ incidents }: IncidentsProps) => (
+ Incidents}>
+ {incidents.length ? (
+
+ ) : (
+
+ )}
+
+);
+
+type EscalationProps = {
+ escalation: PagerDutyUserData[];
+};
+
+const EscalationUsers = ({ escalation }: EscalationProps) => {
+ const classes = useStyles();
+ const escalations = escalation.map((user, index) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ));
+ return {escalations}
;
+};
+
+const EscalationUsersEmptyState = () => {
+ const classes = useStyles();
+ return (
+
+
+
+
+
+
+
+
+ );
+};
+
+const EscalationPolicy = ({ escalation }: EscalationProps) => (
+ Escalation Policy}>
+ {escalation.length ? (
+
+ ) : (
+
+ )}
+
+);
+
+type CardBodyProps = {
+ entity: Entity;
+ data: PagerDutyData;
+};
+
+const CardBody = ({ entity, data }: CardBodyProps) => {
+ const { pagerDutyServices } = data;
+ const classes = useStyles();
+
+ if (!pagerDutyServices.length) {
+ return (
+
+ 'The specified PagerDuty integration key does not match any PagerDuty
+ service.'
+
+ );
+ }
+
+ const { activeIncidents, escalationPolicy } = pagerDutyServices[0];
+
+ return (
+ <>
+
+
+
+ {/* */}
+
+ >
+ );
+};
+
+type PagerDutyIncident = {
+ id: string;
+ title: string;
+ status: string;
+ createdAt: string;
+ homepageUrl: string;
+ assignees: Partial[];
+};
+
+type PagerDutyUserData = {
+ email: string;
+ name: string;
+ homepageUrl: string;
+ id: string;
+};
+
+type PagerDutyServicesData = {
+ activeIncidents: PagerDutyIncident[];
+ escalationPolicy: PagerDutyUserData[];
+ id: string;
+ name: string;
+ homepageUrl: string;
+};
+
+type PagerDutyData = {
+ pagerDutyServices: PagerDutyServicesData[];
+};
+
+type Props = {
+ entity: Entity;
+};
+
+export const PagerDutyServiceCard = ({ entity }: Props) => {
+ // TODO: fetch this data
+ const mockData: PagerDutyData = {
+ pagerDutyServices: [
+ {
+ activeIncidents: [
+ {
+ id: 'id',
+ title: 'something happened',
+ status: 'triggered',
+ createdAt: '2020:01:01',
+ homepageUrl: 'url',
+ assignees: [
+ {
+ name: 'name',
+ },
+ ],
+ },
+ ],
+ escalationPolicy: [
+ {
+ email: 'user@example.com',
+ name: 'Name',
+ homepageUrl: 'https://spotify.pagerduty.com/users/FOO',
+ id: 'user id',
+ },
+ ],
+ id: 'id',
+ name: 'name',
+ homepageUrl: 'https://spotify.pagety.com/service-directory/BAR',
+ },
+ ],
+ };
+
+ return (
+
+
+
+ );
+};
+
+export const isPluginApplicableToEntity = (entity: Entity) => true;
diff --git a/plugins/pagerduty/src/components/pd.svg b/plugins/pagerduty/src/components/pd.svg
new file mode 100644
index 0000000000..237ffa2011
--- /dev/null
+++ b/plugins/pagerduty/src/components/pd.svg
@@ -0,0 +1,13 @@
+
+
+
+ pd_icon
+ Created with Sketch.
+
+
+
+
+
+
+
+
diff --git a/plugins/pagerduty/src/index.ts b/plugins/pagerduty/src/index.ts
index 224e293890..b24018887a 100644
--- a/plugins/pagerduty/src/index.ts
+++ b/plugins/pagerduty/src/index.ts
@@ -14,3 +14,7 @@
* limitations under the License.
*/
export { plugin } from './plugin';
+export {
+ isPluginApplicableToEntity,
+ PagerDutyServiceCard,
+} from './components/PagerDutyServiceCard';
From e8854140c3f8af226f3d2a1bf8acaadb562deab2 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Tue, 20 Oct 2020 09:56:55 +0200
Subject: [PATCH 03/52] add Pagerduty card to entity overview
---
packages/app/src/components/catalog/EntityPage.tsx | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx
index f30e7ff34f..51467fc2bb 100644
--- a/packages/app/src/components/catalog/EntityPage.tsx
+++ b/packages/app/src/components/catalog/EntityPage.tsx
@@ -66,6 +66,10 @@ import {
isPluginApplicableToEntity as isPullRequestsAvailable,
PullRequestsStatsCard,
} from '@roadiehq/backstage-plugin-github-pull-requests';
+import {
+ isPluginApplicableToEntity as isPagerDutyAvailable,
+ PagerDutyServiceCard,
+} from '@backstage/plugin-pagerduty';
const CICDSwitcher = ({ entity }: { entity: Entity }) => {
// This component is just an example of how you can implement your company's logic in entity page.
@@ -131,6 +135,11 @@ const OverviewContent = ({ entity }: { entity: Entity }) => (
+ {isPagerDutyAvailable(entity) && (
+
+
+
+ )}
{isGitHubAvailable(entity) && (
<>
From 82fbe62743b45cbab7d3bac590532f1baabdbb64 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Tue, 20 Oct 2020 11:21:18 +0200
Subject: [PATCH 04/52] splitted up component to separate files
---
.../pagerduty/src/components/Escalation.tsx | 112 ++++++++
.../pagerduty/src/components/Incidents.tsx | 120 +++++++++
.../src/components/PagerDutyServiceCard.tsx | 253 ++----------------
plugins/pagerduty/src/components/types.tsx | 27 ++
4 files changed, 280 insertions(+), 232 deletions(-)
create mode 100644 plugins/pagerduty/src/components/Escalation.tsx
create mode 100644 plugins/pagerduty/src/components/Incidents.tsx
create mode 100644 plugins/pagerduty/src/components/types.tsx
diff --git a/plugins/pagerduty/src/components/Escalation.tsx b/plugins/pagerduty/src/components/Escalation.tsx
new file mode 100644
index 0000000000..217b93a400
--- /dev/null
+++ b/plugins/pagerduty/src/components/Escalation.tsx
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 {
+ List,
+ ListSubheader,
+ ListItem,
+ ListItemIcon,
+ ListItemSecondaryAction,
+ Tooltip,
+ ListItemText,
+ makeStyles,
+ IconButton,
+} from '@material-ui/core';
+import UserIcon from '@material-ui/icons/Person';
+import EmailIcon from '@material-ui/icons/Email';
+import { StatusWarning } from '@backstage/core';
+import Pagerduty from './pd.svg';
+import { PagerDutyUserData } from './types';
+
+const useStyles = makeStyles({
+ denseListIcon: {
+ marginRight: 0,
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ svgButtonImage: {
+ height: '1em',
+ },
+});
+
+type EscalationUserProps = {
+ user: PagerDutyUserData;
+};
+
+const EscalationUser = ({ user }: EscalationUserProps) => {
+ const classes = useStyles();
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const EscalationUsersEmptyState = () => {
+ const classes = useStyles();
+ return (
+
+
+
+
+
+
+
+
+ );
+};
+
+type EscalationPolicyProps = {
+ escalation: PagerDutyUserData[];
+};
+
+export const EscalationPolicy = ({ escalation }: EscalationPolicyProps) => (
+ Escalation Policy}>
+ {escalation.length ? (
+ escalation.map((user, index) => (
+
+ ))
+ ) : (
+
+ )}
+
+);
diff --git a/plugins/pagerduty/src/components/Incidents.tsx b/plugins/pagerduty/src/components/Incidents.tsx
new file mode 100644
index 0000000000..6f6d194a77
--- /dev/null
+++ b/plugins/pagerduty/src/components/Incidents.tsx
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 {
+ List,
+ ListItem,
+ ListItemIcon,
+ ListItemSecondaryAction,
+ Tooltip,
+ ListItemText,
+ makeStyles,
+ IconButton,
+ ListSubheader,
+} from '@material-ui/core';
+import { StatusError, StatusWarning, StatusOK } from '@backstage/core';
+import Pagerduty from './pd.svg';
+import moment from 'moment';
+
+const useStyles = makeStyles({
+ denseListIcon: {
+ marginRight: 0,
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ svgButtonImage: {
+ height: '1em',
+ },
+});
+
+const IncidentsEmptyState = () => {
+ const classes = useStyles();
+ return (
+
+
+
+
+
+
+
+
+ );
+};
+
+type IncidentListProps = {
+ incidents: any;
+};
+
+const IncidentList = ({ incidents }: IncidentListProps) => {
+ const classes = useStyles();
+ return incidents.map((incident: any) => (
+
+
+
+
+ {incident.status === 'triggered' ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ Created {moment(incident.createdAt).fromNow()}, assigned to{' '}
+ {(incident.assignees.length && incident.assignees[0].name) ||
+ 'nobody'}
+
+ }
+ />
+
+
+
+
+
+
+
+
+ ));
+};
+
+type IncidentsProps = {
+ incidents: Array;
+};
+
+export const Incidents = ({ incidents }: IncidentsProps) => (
+ Incidents}>
+ {incidents.length ? (
+
+ ) : (
+
+ )}
+
+);
diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
index 3dadacadfb..cd51c4bcee 100644
--- a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
+++ b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
@@ -14,237 +14,17 @@
* limitations under the License.
*/
import React from 'react';
-import {
- InfoCard,
- StatusError,
- StatusWarning,
- StatusOK,
-} from '@backstage/core';
+import { InfoCard, MissingAnnotationEmptyState } from '@backstage/core';
import { Entity } from '@backstage/catalog-model';
-import {
- List,
- ListSubheader,
- ListItem,
- ListItemIcon,
- ListItemSecondaryAction,
- Tooltip,
- ListItemText,
- makeStyles,
- IconButton,
-} from '@material-ui/core';
-import moment from 'moment';
-import Pagerduty from './pd.svg';
-import UserIcon from '@material-ui/icons/Person';
-import EmailIcon from '@material-ui/icons/Email';
+import { Grid, Button } from '@material-ui/core';
+import { Incidents } from './Incidents';
+import { EscalationPolicy } from './Escalation';
+import { PagerDutyData } from './types';
-const useStyles = makeStyles({
- buttonContainer: {},
- denseListIcon: {
- marginRight: 0,
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center',
- justifyContent: 'center',
- },
- svgButtonImage: {
- height: '1em',
- },
-});
+const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key';
-type IncidentListProps = {
- incidents: any;
-};
-
-const IncidentList = ({ incidents }: IncidentListProps) => {
- const classes = useStyles();
- return incidents.map((incident: any) => (
-
-
-
-
- {incident.status === 'triggered' ? (
-
- ) : (
-
- )}
-
-
-
-
- Created {moment(incident.createdAt).fromNow()}, assigned to{' '}
- {(incident.assignees.length && incident.assignees[0].name) ||
- 'nobody'}
-
- }
- />
-
-
-
-
-
-
-
-
- ));
-};
-
-const IncidentsEmptyState = () => {
- const classes = useStyles();
- return (
-
-
-
-
-
-
-
-
- );
-};
-
-type IncidentsProps = {
- incidents: Array;
-};
-export const Incidents = ({ incidents }: IncidentsProps) => (
- Incidents}>
- {incidents.length ? (
-
- ) : (
-
- )}
-
-);
-
-type EscalationProps = {
- escalation: PagerDutyUserData[];
-};
-
-const EscalationUsers = ({ escalation }: EscalationProps) => {
- const classes = useStyles();
- const escalations = escalation.map((user, index) => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ));
- return {escalations}
;
-};
-
-const EscalationUsersEmptyState = () => {
- const classes = useStyles();
- return (
-
-
-
-
-
-
-
-
- );
-};
-
-const EscalationPolicy = ({ escalation }: EscalationProps) => (
- Escalation Policy}>
- {escalation.length ? (
-
- ) : (
-
- )}
-
-);
-
-type CardBodyProps = {
- entity: Entity;
- data: PagerDutyData;
-};
-
-const CardBody = ({ entity, data }: CardBodyProps) => {
- const { pagerDutyServices } = data;
- const classes = useStyles();
-
- if (!pagerDutyServices.length) {
- return (
-
- 'The specified PagerDuty integration key does not match any PagerDuty
- service.'
-
- );
- }
-
- const { activeIncidents, escalationPolicy } = pagerDutyServices[0];
-
- return (
- <>
-
-
-
- {/* */}
-
- >
- );
-};
-
-type PagerDutyIncident = {
- id: string;
- title: string;
- status: string;
- createdAt: string;
- homepageUrl: string;
- assignees: Partial[];
-};
-
-type PagerDutyUserData = {
- email: string;
- name: string;
- homepageUrl: string;
- id: string;
-};
-
-type PagerDutyServicesData = {
- activeIncidents: PagerDutyIncident[];
- escalationPolicy: PagerDutyUserData[];
- id: string;
- name: string;
- homepageUrl: string;
-};
-
-type PagerDutyData = {
- pagerDutyServices: PagerDutyServicesData[];
-};
+export const isPluginApplicableToEntity = (entity: Entity) =>
+ Boolean(entity.metadata.annotations?.[PAGERDUTY_INTEGRATION_KEY]);
type Props = {
entity: Entity;
@@ -284,11 +64,20 @@ export const PagerDutyServiceCard = ({ entity }: Props) => {
],
};
- return (
+ const { activeIncidents, escalationPolicy } = mockData.pagerDutyServices[0];
+
+ return isPluginApplicableToEntity(entity) ? (
+
+ ) : (
-
+
+
+
+ {/* */}
+
+ Trigger Alarm
+
+
);
};
-
-export const isPluginApplicableToEntity = (entity: Entity) => true;
diff --git a/plugins/pagerduty/src/components/types.tsx b/plugins/pagerduty/src/components/types.tsx
new file mode 100644
index 0000000000..b3b94f0200
--- /dev/null
+++ b/plugins/pagerduty/src/components/types.tsx
@@ -0,0 +1,27 @@
+export type PagerDutyIncident = {
+ id: string;
+ title: string;
+ status: string;
+ createdAt: string;
+ homepageUrl: string;
+ assignees: Partial[];
+};
+
+export type PagerDutyUserData = {
+ email: string;
+ name: string;
+ homepageUrl: string;
+ id: string;
+};
+
+export type PagerDutyServicesData = {
+ activeIncidents: PagerDutyIncident[];
+ escalationPolicy: any; // PagerDutyUserData[];
+ id: string;
+ name: string;
+ homepageUrl: string;
+};
+
+export type PagerDutyData = {
+ pagerDutyServices: PagerDutyServicesData[];
+};
From 4dba9ea97ae30e1e5b83ee6dcf251c141b41519c Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Wed, 21 Oct 2020 10:04:05 +0200
Subject: [PATCH 05/52] add TriggerButton and TriggerDialog, refactor them
---
.../src/components/PagerDutyServiceCard.tsx | 21 ++-
.../src/components/TriggerButton.tsx | 68 +++++++++
.../src/components/TriggerDialog.tsx | 131 ++++++++++++++++++
3 files changed, 213 insertions(+), 7 deletions(-)
create mode 100644 plugins/pagerduty/src/components/TriggerButton.tsx
create mode 100644 plugins/pagerduty/src/components/TriggerDialog.tsx
diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
index cd51c4bcee..e60575ee9b 100644
--- a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
+++ b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
@@ -16,10 +16,11 @@
import React from 'react';
import { InfoCard, MissingAnnotationEmptyState } from '@backstage/core';
import { Entity } from '@backstage/catalog-model';
-import { Grid, Button } from '@material-ui/core';
+import { Grid } from '@material-ui/core';
import { Incidents } from './Incidents';
import { EscalationPolicy } from './Escalation';
import { PagerDutyData } from './types';
+import { TriggerButton } from './TriggerButton';
const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key';
@@ -64,19 +65,25 @@ export const PagerDutyServiceCard = ({ entity }: Props) => {
],
};
- const { activeIncidents, escalationPolicy } = mockData.pagerDutyServices[0];
+ const {
+ activeIncidents,
+ escalationPolicy,
+ homepageUrl,
+ } = mockData.pagerDutyServices[0];
+
+ const link = {
+ title: 'View in PagerDuty',
+ link: homepageUrl,
+ };
return isPluginApplicableToEntity(entity) ? (
) : (
-
+
- {/* */}
-
- Trigger Alarm
-
+
);
diff --git a/plugins/pagerduty/src/components/TriggerButton.tsx b/plugins/pagerduty/src/components/TriggerButton.tsx
new file mode 100644
index 0000000000..578b86d50b
--- /dev/null
+++ b/plugins/pagerduty/src/components/TriggerButton.tsx
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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, { useState } from 'react';
+import { Button } from '@material-ui/core';
+import { TriggerDialog } from './TriggerDialog';
+import { Entity } from '@backstage/catalog-model';
+
+type Props = {
+ entity: Entity;
+};
+
+export const TriggerButton = ({ entity }: Props) => {
+ const [showDialog, setShowDialog] = useState(false);
+
+ const handleDialog = () => {
+ setShowDialog(!showDialog);
+ };
+
+ // const onTriggerAlarm = async (description: string) => {
+ // try {
+ // //TODO: call method from pagerduty client
+ // alertApi.post({
+ // message: `Alarm successfully triggered by ${userId}`,
+ // });
+ // } catch (error) {
+ // alertApi.post({
+ // message: `Failed to trigger alarm, ${error.message}`,
+ // severity: 'error',
+ // });
+ // throw error;
+ // }
+ // };
+
+ return (
+ <>
+
+ Trigger alarm
+
+ {showDialog && (
+
+ )}
+ >
+ );
+};
diff --git a/plugins/pagerduty/src/components/TriggerDialog.tsx b/plugins/pagerduty/src/components/TriggerDialog.tsx
new file mode 100644
index 0000000000..29378cce63
--- /dev/null
+++ b/plugins/pagerduty/src/components/TriggerDialog.tsx
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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, { useState, useEffect } from 'react';
+import {
+ Dialog,
+ DialogTitle,
+ TextField,
+ makeStyles,
+ DialogActions,
+ Button,
+ DialogContent,
+ FormControl,
+ FormHelperText,
+} from '@material-ui/core';
+import { Progress, useApi, alertApiRef, identityApiRef } from '@backstage/core';
+import { useAsyncFn } from 'react-use';
+
+const useStyles = makeStyles({
+ warningText: {
+ border: '1px solid rgba(245, 155, 35, 0.5)',
+ backgroundColor: 'rgba(245, 155, 35, 0.2)',
+ padding: '0.5em 1em',
+ },
+});
+
+type Props = {
+ name: string;
+ integrationKey: string;
+ onClose: () => void;
+};
+
+export const TriggerDialog = ({ name, integrationKey, onClose }: Props) => {
+ const classes = useStyles();
+ const [description, setDescription] = useState('');
+ const alertApi = useApi(alertApiRef);
+ const identityApi = useApi(identityApiRef);
+ const userId = identityApi.getUserId();
+
+ const descriptionChanged = (event: any) => {
+ setDescription(event.target.value);
+ };
+
+ const promiseFunc = () =>
+ new Promise((resolve, reject) => {
+ setTimeout(() => {
+ reject('Inside test await');
+ }, 1000);
+ });
+
+ const [{ value, loading, error }, triggerAlarm] = useAsyncFn(async () => {
+ return await promiseFunc();
+ });
+
+ useEffect(() => {
+ if (value) {
+ alertApi.post({
+ message: `Alarm successfully triggered by ${userId}`,
+ });
+ onClose();
+ }
+
+ if (error) {
+ alertApi.post({
+ message: `Failed to trigger alarm, ${error.message}`,
+ severity: 'error',
+ });
+ }
+ }, [value, error]);
+
+ return (
+
+
+ This action will send PagerDuty alarms and notifications to on-call
+ people responsible for {name}.
+
+
+
+ Note: If the issue you are seeing does not need urgent attention,
+ please get in touch with the responsible team using their preferred
+ communications channel. For most views, you can find links to support
+ and information channels by clicking the support button in the top
+ right corner of the page. If the issue is urgent, please don't
+ hesitate to trigger the alert.
+
+
+ Please describe the problem you want to report. Be as descriptive as
+ possible. Your Spotify username and a reference to the current page
+ will automatically be sent amended to the alarm so that we can debug
+ the issue and reach out to you if necessary.
+
+
+
+
+
+ Trigger
+
+
+ Close
+
+
+ {loading && }
+
+ );
+};
From 4e93108ba4d94ea9818777604e02f9b3831aff08 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Wed, 21 Oct 2020 14:12:52 +0200
Subject: [PATCH 06/52] trigger pager alarm
---
plugins/pagerduty/package.json | 4 +-
plugins/pagerduty/src/api/pagerDutyClient.ts | 73 +++++++++++++++++++
.../src/{components => assets}/pd.svg | 0
.../pagerduty/src/components/Escalation.tsx | 2 +-
.../pagerduty/src/components/Incidents.tsx | 2 +-
.../src/components/PagerDutyServiceCard.tsx | 4 +-
.../src/components/TriggerButton.tsx | 21 +-----
.../src/components/TriggerDialog.tsx | 31 ++++----
8 files changed, 99 insertions(+), 38 deletions(-)
create mode 100644 plugins/pagerduty/src/api/pagerDutyClient.ts
rename plugins/pagerduty/src/{components => assets}/pd.svg (100%)
diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json
index 33054c6163..e5d0264be3 100644
--- a/plugins/pagerduty/package.json
+++ b/plugins/pagerduty/package.json
@@ -22,12 +22,14 @@
"dependencies": {
"@backstage/core": "^0.1.1-alpha.25",
"@backstage/theme": "^0.1.1-alpha.25",
+ "@backstage/catalog-model": "^0.1.1-alpha.25",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"react": "^16.13.1",
"react-dom": "^16.13.1",
- "react-use": "^15.3.3"
+ "react-use": "^15.3.3",
+ "moment": "^2.27.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.25",
diff --git a/plugins/pagerduty/src/api/pagerDutyClient.ts b/plugins/pagerduty/src/api/pagerDutyClient.ts
new file mode 100644
index 0000000000..d5db842dab
--- /dev/null
+++ b/plugins/pagerduty/src/api/pagerDutyClient.ts
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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.
+ */
+
+type Options = {
+ method: string;
+ headers: {
+ 'Content-Type': string;
+ Accept: string;
+ };
+ body: string;
+};
+
+const request = async (
+ url: string,
+ options: Options,
+): Promise => {
+ 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 await response.json();
+};
+
+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({
+ event_action: 'trigger',
+ routing_key: integrationKey,
+ client: 'Backstage',
+ client_url: source,
+ payload: {
+ summary: description,
+ source: source,
+ severity: 'error',
+ class: 'manual trigger',
+ custom_details: {
+ user: userName,
+ },
+ },
+ }),
+ };
+
+ return request('https://events.pagerduty.com/v2/enqueue', options);
+}
diff --git a/plugins/pagerduty/src/components/pd.svg b/plugins/pagerduty/src/assets/pd.svg
similarity index 100%
rename from plugins/pagerduty/src/components/pd.svg
rename to plugins/pagerduty/src/assets/pd.svg
diff --git a/plugins/pagerduty/src/components/Escalation.tsx b/plugins/pagerduty/src/components/Escalation.tsx
index 217b93a400..74e253d516 100644
--- a/plugins/pagerduty/src/components/Escalation.tsx
+++ b/plugins/pagerduty/src/components/Escalation.tsx
@@ -29,7 +29,7 @@ import {
import UserIcon from '@material-ui/icons/Person';
import EmailIcon from '@material-ui/icons/Email';
import { StatusWarning } from '@backstage/core';
-import Pagerduty from './pd.svg';
+import Pagerduty from '../assets/pd.svg';
import { PagerDutyUserData } from './types';
const useStyles = makeStyles({
diff --git a/plugins/pagerduty/src/components/Incidents.tsx b/plugins/pagerduty/src/components/Incidents.tsx
index 6f6d194a77..98a26c66db 100644
--- a/plugins/pagerduty/src/components/Incidents.tsx
+++ b/plugins/pagerduty/src/components/Incidents.tsx
@@ -27,7 +27,7 @@ import {
ListSubheader,
} from '@material-ui/core';
import { StatusError, StatusWarning, StatusOK } from '@backstage/core';
-import Pagerduty from './pd.svg';
+import Pagerduty from '../assets/pd.svg';
import moment from 'moment';
const useStyles = makeStyles({
diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
index e60575ee9b..e0564d76ce 100644
--- a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
+++ b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
@@ -22,7 +22,7 @@ import { EscalationPolicy } from './Escalation';
import { PagerDutyData } from './types';
import { TriggerButton } from './TriggerButton';
-const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key';
+export const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key';
export const isPluginApplicableToEntity = (entity: Entity) =>
Boolean(entity.metadata.annotations?.[PAGERDUTY_INTEGRATION_KEY]);
@@ -76,7 +76,7 @@ export const PagerDutyServiceCard = ({ entity }: Props) => {
link: homepageUrl,
};
- return isPluginApplicableToEntity(entity) ? (
+ return !isPluginApplicableToEntity(entity) ? (
) : (
diff --git a/plugins/pagerduty/src/components/TriggerButton.tsx b/plugins/pagerduty/src/components/TriggerButton.tsx
index 578b86d50b..eb051e68fa 100644
--- a/plugins/pagerduty/src/components/TriggerButton.tsx
+++ b/plugins/pagerduty/src/components/TriggerButton.tsx
@@ -18,6 +18,7 @@ import React, { useState } from 'react';
import { Button } from '@material-ui/core';
import { TriggerDialog } from './TriggerDialog';
import { Entity } from '@backstage/catalog-model';
+import { PAGERDUTY_INTEGRATION_KEY } from './PagerDutyServiceCard';
type Props = {
entity: Entity;
@@ -30,21 +31,6 @@ export const TriggerButton = ({ entity }: Props) => {
setShowDialog(!showDialog);
};
- // const onTriggerAlarm = async (description: string) => {
- // try {
- // //TODO: call method from pagerduty client
- // alertApi.post({
- // message: `Alarm successfully triggered by ${userId}`,
- // });
- // } catch (error) {
- // alertApi.post({
- // message: `Failed to trigger alarm, ${error.message}`,
- // severity: 'error',
- // });
- // throw error;
- // }
- // };
-
return (
<>
{
{showDialog && (
)}
diff --git a/plugins/pagerduty/src/components/TriggerDialog.tsx b/plugins/pagerduty/src/components/TriggerDialog.tsx
index 29378cce63..7dbb0be5f0 100644
--- a/plugins/pagerduty/src/components/TriggerDialog.tsx
+++ b/plugins/pagerduty/src/components/TriggerDialog.tsx
@@ -23,11 +23,10 @@ import {
DialogActions,
Button,
DialogContent,
- FormControl,
- FormHelperText,
} from '@material-ui/core';
import { Progress, useApi, alertApiRef, identityApiRef } from '@backstage/core';
import { useAsyncFn } from 'react-use';
+import { triggerPagerDutyAlarm } from '../api/pagerDutyClient';
const useStyles = makeStyles({
warningText: {
@@ -54,16 +53,16 @@ export const TriggerDialog = ({ name, integrationKey, onClose }: Props) => {
setDescription(event.target.value);
};
- const promiseFunc = () =>
- new Promise((resolve, reject) => {
- setTimeout(() => {
- reject('Inside test await');
- }, 1000);
- });
-
- const [{ value, loading, error }, triggerAlarm] = useAsyncFn(async () => {
- return await promiseFunc();
- });
+ const [{ value, loading, error }, triggerAlarm] = useAsyncFn(
+ async (desc: string) => {
+ return await triggerPagerDutyAlarm(
+ integrationKey,
+ window.location.toString(),
+ desc,
+ userId,
+ );
+ },
+ );
useEffect(() => {
if (value) {
@@ -75,14 +74,14 @@ export const TriggerDialog = ({ name, integrationKey, onClose }: Props) => {
if (error) {
alertApi.post({
- message: `Failed to trigger alarm, ${error.message}`,
+ message: `Failed to trigger alarm. ${error.message}`,
severity: 'error',
});
}
- }, [value, error]);
+ }, [value, error, alertApi, onClose, userId]);
return (
-
+
This action will send PagerDuty alarms and notifications to on-call
people responsible for {name}.
@@ -117,7 +116,7 @@ export const TriggerDialog = ({ name, integrationKey, onClose }: Props) => {
id="trigger"
color="secondary"
disabled={!description || loading}
- onClick={triggerAlarm}
+ onClick={() => triggerAlarm(description)}
>
Trigger
From fe2d8ac7152fc3a2d88df1dea66750f4dffa732a Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Thu, 22 Oct 2020 10:18:46 +0200
Subject: [PATCH 07/52] wip:fetching data from Api
---
.../app/src/components/catalog/EntityPage.tsx | 10 +++----
plugins/pagerduty/src/api/pagerDutyClient.ts | 29 +++++++++++++++++--
.../src/components/PagerDutyServiceCard.tsx | 24 ++++++++++++++-
3 files changed, 54 insertions(+), 9 deletions(-)
diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx
index 51467fc2bb..7629c86a1e 100644
--- a/packages/app/src/components/catalog/EntityPage.tsx
+++ b/packages/app/src/components/catalog/EntityPage.tsx
@@ -135,11 +135,11 @@ const OverviewContent = ({ entity }: { entity: Entity }) => (
- {isPagerDutyAvailable(entity) && (
-
-
-
- )}
+ {/* {isPagerDutyAvailable(entity) && ( */}
+
+
+
+ {/* )} */}
{isGitHubAvailable(entity) && (
<>
diff --git a/plugins/pagerduty/src/api/pagerDutyClient.ts b/plugins/pagerduty/src/api/pagerDutyClient.ts
index d5db842dab..fa42c82f9e 100644
--- a/plugins/pagerduty/src/api/pagerDutyClient.ts
+++ b/plugins/pagerduty/src/api/pagerDutyClient.ts
@@ -14,18 +14,22 @@
* limitations under the License.
*/
+const API_URL = 'https://api.pagerduty.com';
+const EVENTS_API_URL = 'https://events.pagerduty.com/v2';
+
type Options = {
method: string;
headers: {
'Content-Type': string;
Accept: string;
+ Authorization?: string;
};
- body: string;
+ body?: string;
};
const request = async (
url: string,
- options: Options,
+ options: any, //Options,
): Promise => {
const response = await fetch(url, options);
@@ -40,6 +44,25 @@ const request = async (
return await response.json();
};
+export const getServices = async (token: string, integrationKey: string) => {
+ const options = {
+ method: 'GET',
+ headers: {
+ Authorization: `Token token=${token}`,
+ Accept: 'application/vnd.pagerduty+json;version=2',
+ 'Content-Type': 'application/json',
+ },
+ // query: {
+ // query: encodeURIComponent(`key:${integrationKey}`),
+ // },
+ };
+
+ return request(
+ `${API_URL}/services/?query=key%253A238b701cc9d048f0bdd828355178eafe`,
+ options,
+ );
+};
+
export function triggerPagerDutyAlarm(
integrationKey: string,
source: string,
@@ -69,5 +92,5 @@ export function triggerPagerDutyAlarm(
}),
};
- return request('https://events.pagerduty.com/v2/enqueue', options);
+ return request(`${EVENTS_API_URL}/enqueue`, options);
}
diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
index e0564d76ce..917ae27090 100644
--- a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
+++ b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
@@ -14,13 +14,20 @@
* limitations under the License.
*/
import React from 'react';
-import { InfoCard, MissingAnnotationEmptyState } from '@backstage/core';
+import {
+ InfoCard,
+ MissingAnnotationEmptyState,
+ useApi,
+ configApiRef,
+} from '@backstage/core';
import { Entity } from '@backstage/catalog-model';
import { Grid } from '@material-ui/core';
import { Incidents } from './Incidents';
import { EscalationPolicy } from './Escalation';
import { PagerDutyData } from './types';
import { TriggerButton } from './TriggerButton';
+import { useAsync } from 'react-use';
+import { getServices } from '../api/pagerDutyClient';
export const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key';
@@ -32,6 +39,20 @@ type Props = {
};
export const PagerDutyServiceCard = ({ entity }: Props) => {
+ const configApi = useApi(configApiRef);
+ const pagerDutyToken =
+ configApi.getOptionalString('pagerduty.api_token') ?? undefined;
+
+ console.log({ pagerDutyToken });
+ const { value, loading, error } = useAsync(async () => {
+ return await getServices(
+ pagerDutyToken!,
+ entity.metadata.annotations![PAGERDUTY_INTEGRATION_KEY],
+ );
+ });
+ if (value) console.log(value);
+ if (error) throw new Error(`Error in getting services, ${error}`);
+
// TODO: fetch this data
const mockData: PagerDutyData = {
pagerDutyServices: [
@@ -85,6 +106,7 @@ export const PagerDutyServiceCard = ({ entity }: Props) => {
+ {/* todo show something when we dont have token */}
);
};
From 0609e726936e457c0407fa83ba8783409275f864 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Thu, 22 Oct 2020 15:12:38 +0200
Subject: [PATCH 08/52] add pagerduty backend
---
packages/backend/package.json | 1 +
packages/backend/src/index.ts | 3 +
packages/backend/src/plugins/pagerduty.ts | 27 ++++++++
plugins/pagerduty-backend/.eslintrc.js | 3 +
plugins/pagerduty-backend/README.md | 13 ++++
plugins/pagerduty-backend/package.json | 40 ++++++++++++
plugins/pagerduty-backend/src/index.ts | 17 +++++
plugins/pagerduty-backend/src/run.ts | 33 ++++++++++
.../src/service/router.test.ts | 45 ++++++++++++++
.../pagerduty-backend/src/service/router.ts | 42 +++++++++++++
.../src/service/standaloneServer.ts | 62 +++++++++++++++++++
plugins/pagerduty-backend/src/setupTests.ts | 18 ++++++
12 files changed, 304 insertions(+)
create mode 100644 packages/backend/src/plugins/pagerduty.ts
create mode 100644 plugins/pagerduty-backend/.eslintrc.js
create mode 100644 plugins/pagerduty-backend/README.md
create mode 100644 plugins/pagerduty-backend/package.json
create mode 100644 plugins/pagerduty-backend/src/index.ts
create mode 100644 plugins/pagerduty-backend/src/run.ts
create mode 100644 plugins/pagerduty-backend/src/service/router.test.ts
create mode 100644 plugins/pagerduty-backend/src/service/router.ts
create mode 100644 plugins/pagerduty-backend/src/service/standaloneServer.ts
create mode 100644 plugins/pagerduty-backend/src/setupTests.ts
diff --git a/packages/backend/package.json b/packages/backend/package.json
index ef55b007a7..c31629e82e 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -31,6 +31,7 @@
"@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.25",
"@backstage/plugin-sentry-backend": "^0.1.1-alpha.25",
"@backstage/plugin-techdocs-backend": "^0.1.1-alpha.25",
+ "@backstage/plugin-pagerduty-backend": "^0.1.1-alpha.25",
"@gitbeaker/node": "^23.5.0",
"@octokit/rest": "^18.0.0",
"azure-devops-node-api": "^10.1.1",
diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts
index 18fd44f089..0ca17c7d7f 100644
--- a/packages/backend/src/index.ts
+++ b/packages/backend/src/index.ts
@@ -46,6 +46,7 @@ import techdocs from './plugins/techdocs';
import graphql from './plugins/graphql';
import app from './plugins/app';
import { PluginEnvironment } from './types';
+import pagerduty from './plugins/pagerduty';
function makeCreateEnv(config: ConfigReader) {
const root = getRootLogger();
@@ -79,6 +80,7 @@ async function main() {
const kubernetesEnv = useHotMemoize(module, () => createEnv('kubernetes'));
const graphqlEnv = useHotMemoize(module, () => createEnv('graphql'));
const appEnv = useHotMemoize(module, () => createEnv('app'));
+ const pagerdutyEnv = useHotMemoize(module, () => createEnv('pagerduty'));
const apiRouter = Router();
apiRouter.use('/catalog', await catalog(catalogEnv));
@@ -90,6 +92,7 @@ async function main() {
apiRouter.use('/kubernetes', await kubernetes(kubernetesEnv));
apiRouter.use('/proxy', await proxy(proxyEnv));
apiRouter.use('/graphql', await graphql(graphqlEnv));
+ apiRouter.use('/pagerduty', await pagerduty(pagerdutyEnv));
apiRouter.use(notFoundHandler());
const service = createServiceBuilder(module)
diff --git a/packages/backend/src/plugins/pagerduty.ts b/packages/backend/src/plugins/pagerduty.ts
new file mode 100644
index 0000000000..6dc3d8c9ba
--- /dev/null
+++ b/packages/backend/src/plugins/pagerduty.ts
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { createRouter } from '@backstage/plugin-pagerduty-backend';
+import { PluginEnvironment } from '../types';
+
+export default async function createPlugin({
+ logger,
+ config,
+}: PluginEnvironment) {
+ return await createRouter({
+ logger,
+ config,
+ });
+}
diff --git a/plugins/pagerduty-backend/.eslintrc.js b/plugins/pagerduty-backend/.eslintrc.js
new file mode 100644
index 0000000000..16a033dbc6
--- /dev/null
+++ b/plugins/pagerduty-backend/.eslintrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+ extends: [require.resolve('@backstage/cli/config/eslint.backend')],
+};
diff --git a/plugins/pagerduty-backend/README.md b/plugins/pagerduty-backend/README.md
new file mode 100644
index 0000000000..099a94a85f
--- /dev/null
+++ b/plugins/pagerduty-backend/README.md
@@ -0,0 +1,13 @@
+# pagerduty
+
+Welcome to the pagerduty backend plugin!
+
+_This plugin was created through the Backstage CLI_
+
+## Getting started
+
+Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/pagerduty](http://localhost:3000/pagerduty).
+
+You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
+This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
+It is only meant for local development, and the setup for it can be found inside the [/dev](/dev) directory.
diff --git a/plugins/pagerduty-backend/package.json b/plugins/pagerduty-backend/package.json
new file mode 100644
index 0000000000..cad4ba268e
--- /dev/null
+++ b/plugins/pagerduty-backend/package.json
@@ -0,0 +1,40 @@
+{
+ "name": "@backstage/plugin-pagerduty-backend",
+ "version": "0.1.1-alpha.25",
+ "main": "src/index.ts",
+ "types": "src/index.ts",
+ "license": "Apache-2.0",
+ "publishConfig": {
+ "access": "public",
+ "main": "dist/index.cjs.js",
+ "types": "dist/index.d.ts"
+ },
+ "scripts": {
+ "start": "backstage-cli backend:dev",
+ "build": "backstage-cli backend:build",
+ "lint": "backstage-cli lint",
+ "test": "backstage-cli test",
+ "prepack": "backstage-cli prepack",
+ "postpack": "backstage-cli postpack",
+ "clean": "backstage-cli clean"
+ },
+ "dependencies": {
+ "@backstage/backend-common": "^0.1.1-alpha.25",
+ "@backstage/config": "^0.1.1-alpha.25",
+ "@types/express": "^4.17.6",
+ "express": "^4.17.1",
+ "express-promise-router": "^3.0.3",
+ "winston": "^3.2.1",
+ "node-fetch": "^2.6.1",
+ "yn": "^4.0.0"
+ },
+ "devDependencies": {
+ "@backstage/cli": "^0.1.1-alpha.25",
+ "@types/supertest": "^2.0.8",
+ "supertest": "^4.0.2",
+ "msw": "^0.20.5"
+ },
+ "files": [
+ "dist"
+ ]
+}
diff --git a/plugins/pagerduty-backend/src/index.ts b/plugins/pagerduty-backend/src/index.ts
new file mode 100644
index 0000000000..7612c392a2
--- /dev/null
+++ b/plugins/pagerduty-backend/src/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 * from './service/router';
diff --git a/plugins/pagerduty-backend/src/run.ts b/plugins/pagerduty-backend/src/run.ts
new file mode 100644
index 0000000000..b96989e4b8
--- /dev/null
+++ b/plugins/pagerduty-backend/src/run.ts
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { getRootLogger } from '@backstage/backend-common';
+import yn from 'yn';
+import { startStandaloneServer } from './service/standaloneServer';
+
+const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7000;
+const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
+const logger = getRootLogger();
+
+startStandaloneServer({ port, enableCors, logger }).catch(err => {
+ logger.error(err);
+ process.exit(1);
+});
+
+process.on('SIGINT', () => {
+ logger.info('CTRL+C pressed; exiting.');
+ process.exit(0);
+});
diff --git a/plugins/pagerduty-backend/src/service/router.test.ts b/plugins/pagerduty-backend/src/service/router.test.ts
new file mode 100644
index 0000000000..0aaeafa379
--- /dev/null
+++ b/plugins/pagerduty-backend/src/service/router.test.ts
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { getVoidLogger } from '@backstage/backend-common';
+import express from 'express';
+import request from 'supertest';
+
+import { createRouter } from './router';
+
+describe('createRouter', () => {
+ let app: express.Express;
+
+ beforeAll(async () => {
+ const router = await createRouter({
+ logger: getVoidLogger(),
+ });
+ app = express().use(router);
+ });
+
+ beforeEach(() => {
+ jest.resetAllMocks();
+ });
+
+ describe('GET /health', () => {
+ it('returns ok', async () => {
+ const response = await request(app).get('/health');
+
+ expect(response.status).toEqual(200);
+ expect(response.body).toEqual({ status: 'ok' });
+ });
+ });
+});
diff --git a/plugins/pagerduty-backend/src/service/router.ts b/plugins/pagerduty-backend/src/service/router.ts
new file mode 100644
index 0000000000..eb7cc734c2
--- /dev/null
+++ b/plugins/pagerduty-backend/src/service/router.ts
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { errorHandler } from '@backstage/backend-common';
+import { Config } from '@backstage/config';
+import express from 'express';
+import Router from 'express-promise-router';
+import { Logger } from 'winston';
+
+export interface RouterOptions {
+ logger: Logger;
+ config: Config;
+}
+
+export async function createRouter(
+ options: RouterOptions,
+): Promise {
+ const { logger } = options;
+
+ const router = Router();
+ router.use(express.json());
+
+ router.get('/health', (_, response) => {
+ logger.info('PONG!');
+ response.send({ status: 'ok' });
+ });
+ router.use(errorHandler());
+ return router;
+}
diff --git a/plugins/pagerduty-backend/src/service/standaloneServer.ts b/plugins/pagerduty-backend/src/service/standaloneServer.ts
new file mode 100644
index 0000000000..73f928bef4
--- /dev/null
+++ b/plugins/pagerduty-backend/src/service/standaloneServer.ts
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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.
+ */
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { createServiceBuilder } from '@backstage/backend-common';
+import { Server } from 'http';
+import { Logger } from 'winston';
+import { createRouter } from './router';
+
+export interface ServerOptions {
+ port: number;
+ enableCors: boolean;
+ logger: Logger;
+}
+
+export async function startStandaloneServer(
+ options: ServerOptions,
+): Promise {
+ const logger = options.logger.child({ service: 'pagerduty-backend' });
+ logger.debug('Starting application server...');
+ const router = await createRouter({
+ logger,
+ });
+
+ const service = createServiceBuilder(module)
+ .enableCors({ origin: 'http://localhost:3000' })
+ .addRouter('/pagerduty', router);
+
+ return await service.start().catch(err => {
+ logger.error(err);
+ process.exit(1);
+ });
+}
+
+module.hot?.accept();
diff --git a/plugins/pagerduty-backend/src/setupTests.ts b/plugins/pagerduty-backend/src/setupTests.ts
new file mode 100644
index 0000000000..a5907fd52f
--- /dev/null
+++ b/plugins/pagerduty-backend/src/setupTests.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 {};
+global.fetch = require('node-fetch');
From 6d1f4a996d25f93a6b5a0c5f8f605f58048d3e2f Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Fri, 23 Oct 2020 13:14:08 +0200
Subject: [PATCH 09/52] fetching data from pagerduty endpoint in interval
---
.../pagerduty-backend/src/service/router.ts | 95 ++++++++++++++++++-
1 file changed, 92 insertions(+), 3 deletions(-)
diff --git a/plugins/pagerduty-backend/src/service/router.ts b/plugins/pagerduty-backend/src/service/router.ts
index eb7cc734c2..192a22019a 100644
--- a/plugins/pagerduty-backend/src/service/router.ts
+++ b/plugins/pagerduty-backend/src/service/router.ts
@@ -14,11 +14,79 @@
* limitations under the License.
*/
-import { errorHandler } from '@backstage/backend-common';
+import { errorHandler, useHotCleanup } from '@backstage/backend-common';
import { Config } from '@backstage/config';
import express from 'express';
import Router from 'express-promise-router';
import { Logger } from 'winston';
+import fetch from 'node-fetch';
+
+const router = Router();
+const API_URL = 'https://api.pagerduty.com';
+const EVENTS_API_URL = 'https://events.pagerduty.com/v2';
+
+type Options = {
+ method: string;
+ headers: {
+ 'Content-Type': string;
+ Accept: string;
+ Authorization?: string;
+ };
+ body?: string;
+};
+
+async function request(
+ url: string,
+ options: any, // Options,
+): Promise {
+ // TODO: handle errors better
+ 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 await response.json();
+}
+
+async function services(token?: string) {
+ // TODO: handle missing token differently
+ if (!token) {
+ return 'Missing Token';
+ }
+ const options = {
+ method: 'GET',
+ headers: {
+ Authorization: `Token token=${token}`,
+ Accept: 'application/vnd.pagerduty+json;version=2',
+ 'Content-Type': 'application/json',
+ },
+ };
+ let offset = 0;
+ // TODO: Create type for this data
+ const data: any = [];
+
+ async function fetchData() {
+ const result: any = await request(
+ `${API_URL}/services?offset=${offset}`,
+ options,
+ );
+ data.push(...result.services);
+
+ // TODO: remove offset when we are done
+ if (offset < 4 && result.more) {
+ ++offset;
+ await fetchData();
+ }
+ }
+
+ await fetchData();
+ return data;
+}
export interface RouterOptions {
logger: Logger;
@@ -28,15 +96,36 @@ export interface RouterOptions {
export async function createRouter(
options: RouterOptions,
): Promise {
- const { logger } = options;
+ const { logger, config } = options;
+ const token = config.getOptionalString('pagerduty.api_token') ?? undefined;
+
+ let cachedData: any = [];
+
+ let cancel = false;
+ const intervalId = setInterval(async () => {
+ if (!cancel) {
+ cancel = true;
+ const result = await services(token);
+ cachedData = result;
+ cancel = false;
+ }
+ }, 10000);
+
+ useHotCleanup(module, () => clearInterval(intervalId));
- const router = Router();
router.use(express.json());
+ router.get('/services', async (_, response) => {
+ logger.info('pinged');
+
+ response.send({ services: cachedData, size: cachedData.length });
+ });
+
router.get('/health', (_, response) => {
logger.info('PONG!');
response.send({ status: 'ok' });
});
+
router.use(errorHandler());
return router;
}
From c9c212e064a365f2cffcfc7eb6a10eef5699a0ae Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Fri, 23 Oct 2020 14:59:36 +0200
Subject: [PATCH 10/52] added error handling
---
.../pagerduty-backend/src/service/router.ts | 98 +++++++++++++------
plugins/pagerduty/src/api/pagerDutyClient.ts | 20 ----
.../src/components/PagerDutyServiceCard.tsx | 10 +-
3 files changed, 71 insertions(+), 57 deletions(-)
diff --git a/plugins/pagerduty-backend/src/service/router.ts b/plugins/pagerduty-backend/src/service/router.ts
index 192a22019a..1fbab9b551 100644
--- a/plugins/pagerduty-backend/src/service/router.ts
+++ b/plugins/pagerduty-backend/src/service/router.ts
@@ -20,6 +20,7 @@ import express from 'express';
import Router from 'express-promise-router';
import { Logger } from 'winston';
import fetch from 'node-fetch';
+import { Response } from 'express';
const router = Router();
const API_URL = 'https://api.pagerduty.com';
@@ -38,19 +39,21 @@ type Options = {
async function request(
url: string,
options: any, // Options,
-): Promise {
+): Promise {
// TODO: handle errors better
- const response = await fetch(url, options);
+ 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);
+ 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 await response.json();
+ } catch (error) {
+ throw new Error(error);
}
-
- return await response.json();
}
async function services(token?: string) {
@@ -58,6 +61,7 @@ async function services(token?: string) {
if (!token) {
return 'Missing Token';
}
+
const options = {
method: 'GET',
headers: {
@@ -66,25 +70,38 @@ async function services(token?: string) {
'Content-Type': 'application/json',
},
};
+
let offset = 0;
// TODO: Create type for this data
- const data: any = [];
+ const data: PagerDutyService[] = [];
- async function fetchData() {
- const result: any = await request(
+ async function getData() {
+ const result = await request(
`${API_URL}/services?offset=${offset}`,
options,
);
- data.push(...result.services);
+
+ data.push(
+ ...result.services.map(service => ({
+ id: service.id,
+ name: service.name,
+ homepageUrl: service.html_url,
+ })),
+ );
// TODO: remove offset when we are done
- if (offset < 4 && result.more) {
+ if (offset < 2 && result.more) {
++offset;
- await fetchData();
+ await getData();
}
}
- await fetchData();
+ try {
+ await getData();
+ } catch (error) {
+ throw new Error(error);
+ }
+
return data;
}
@@ -93,6 +110,19 @@ export interface RouterOptions {
config: Config;
}
+type PagerDutyService = {
+ // activeIncidents: any[];
+ // escalationPolicy: any[];
+ id: string;
+ name: string;
+ homepageUrl: string;
+};
+
+type PagerDutyResponse = {
+ services: any[];
+ more: boolean;
+};
+
export async function createRouter(
options: RouterOptions,
): Promise {
@@ -100,14 +130,21 @@ export async function createRouter(
const token = config.getOptionalString('pagerduty.api_token') ?? undefined;
let cachedData: any = [];
+ let errorMessage: string;
let cancel = false;
const intervalId = setInterval(async () => {
if (!cancel) {
cancel = true;
- const result = await services(token);
- cachedData = result;
- cancel = false;
+ try {
+ logger.info('Fetching data from PagerDuty API');
+ cachedData = await services(token);
+ } catch (error) {
+ logger.error(`Failed to fetch data, ${error.message}`);
+ errorMessage = error.message;
+ } finally {
+ cancel = false;
+ }
}
}, 10000);
@@ -115,17 +152,18 @@ export async function createRouter(
router.use(express.json());
- router.get('/services', async (_, response) => {
- logger.info('pinged');
-
- response.send({ services: cachedData, size: cachedData.length });
- });
-
- router.get('/health', (_, response) => {
- logger.info('PONG!');
- response.send({ status: 'ok' });
- });
+ router.get(
+ '/services',
+ async (_, response: Response) => {
+ if (errorMessage) {
+ response.status(500).send({ error: errorMessage });
+ } else {
+ response.send({ services: cachedData });
+ }
+ },
+ );
router.use(errorHandler());
+
return router;
}
diff --git a/plugins/pagerduty/src/api/pagerDutyClient.ts b/plugins/pagerduty/src/api/pagerDutyClient.ts
index fa42c82f9e..dc2176680d 100644
--- a/plugins/pagerduty/src/api/pagerDutyClient.ts
+++ b/plugins/pagerduty/src/api/pagerDutyClient.ts
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-const API_URL = 'https://api.pagerduty.com';
const EVENTS_API_URL = 'https://events.pagerduty.com/v2';
type Options = {
@@ -44,25 +43,6 @@ const request = async (
return await response.json();
};
-export const getServices = async (token: string, integrationKey: string) => {
- const options = {
- method: 'GET',
- headers: {
- Authorization: `Token token=${token}`,
- Accept: 'application/vnd.pagerduty+json;version=2',
- 'Content-Type': 'application/json',
- },
- // query: {
- // query: encodeURIComponent(`key:${integrationKey}`),
- // },
- };
-
- return request(
- `${API_URL}/services/?query=key%253A238b701cc9d048f0bdd828355178eafe`,
- options,
- );
-};
-
export function triggerPagerDutyAlarm(
integrationKey: string,
source: string,
diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
index 917ae27090..b7fde8eae3 100644
--- a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
+++ b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
@@ -27,7 +27,6 @@ import { EscalationPolicy } from './Escalation';
import { PagerDutyData } from './types';
import { TriggerButton } from './TriggerButton';
import { useAsync } from 'react-use';
-import { getServices } from '../api/pagerDutyClient';
export const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key';
@@ -40,15 +39,12 @@ type Props = {
export const PagerDutyServiceCard = ({ entity }: Props) => {
const configApi = useApi(configApiRef);
- const pagerDutyToken =
- configApi.getOptionalString('pagerduty.api_token') ?? undefined;
- console.log({ pagerDutyToken });
const { value, loading, error } = useAsync(async () => {
- return await getServices(
- pagerDutyToken!,
- entity.metadata.annotations![PAGERDUTY_INTEGRATION_KEY],
+ const response = await fetch(
+ `${configApi.getString('backend.baseUrl')}/api/pagerduty/services`,
);
+ return await response.json();
});
if (value) console.log(value);
if (error) throw new Error(`Error in getting services, ${error}`);
From 361049d9c2c7e8209b383db73c28bf6caee6b79a Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Tue, 3 Nov 2020 11:26:23 +0100
Subject: [PATCH 11/52] Refactor component to use pagerduty restApi directly
---
.../app/src/components/catalog/EntityPage.tsx | 10 +-
plugins/pagerduty/src/api/pagerDutyClient.ts | 44 ++++++++
.../pagerduty/src/components/Escalation.tsx | 4 +-
.../pagerduty/src/components/Incidents.tsx | 5 +-
.../src/components/PagerDutyServiceCard.tsx | 105 +++++++++---------
5 files changed, 105 insertions(+), 63 deletions(-)
diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx
index 7629c86a1e..ddc8ed4b13 100644
--- a/packages/app/src/components/catalog/EntityPage.tsx
+++ b/packages/app/src/components/catalog/EntityPage.tsx
@@ -135,11 +135,11 @@ const OverviewContent = ({ entity }: { entity: Entity }) => (
- {/* {isPagerDutyAvailable(entity) && ( */}
-
-
-
- {/* )} */}
+ {isPagerDutyAvailable(entity) && (
+
+
+
+ )}
{isGitHubAvailable(entity) && (
<>
diff --git a/plugins/pagerduty/src/api/pagerDutyClient.ts b/plugins/pagerduty/src/api/pagerDutyClient.ts
index dc2176680d..dbdc6622f4 100644
--- a/plugins/pagerduty/src/api/pagerDutyClient.ts
+++ b/plugins/pagerduty/src/api/pagerDutyClient.ts
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+const API_URL = 'https://api.pagerduty.com';
const EVENTS_API_URL = 'https://events.pagerduty.com/v2';
type Options = {
@@ -43,6 +44,49 @@ const request = async (
return await response.json();
};
+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);
+};
+
+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 response.services[0];
+};
+
+export const getIncidentsByServiceId = async (
+ serviceId: string,
+ token: string,
+) => {
+ return await getByUrl(
+ `${API_URL}/incidents?service_ids[]=${serviceId}`,
+ token,
+ );
+};
+
+export const getOncallByPolicyId = async (policyId: string, token: string) => {
+ return await getByUrl(
+ `${API_URL}/oncalls?include[]=users&escalation_policy_ids[]=${policyId}`,
+ token,
+ );
+};
+
export function triggerPagerDutyAlarm(
integrationKey: string,
source: string,
diff --git a/plugins/pagerduty/src/components/Escalation.tsx b/plugins/pagerduty/src/components/Escalation.tsx
index 74e253d516..69a4543c4b 100644
--- a/plugins/pagerduty/src/components/Escalation.tsx
+++ b/plugins/pagerduty/src/components/Escalation.tsx
@@ -102,8 +102,8 @@ type EscalationPolicyProps = {
export const EscalationPolicy = ({ escalation }: EscalationPolicyProps) => (
Escalation Policy}>
{escalation.length ? (
- escalation.map((user, index) => (
-
+ escalation.map((item, index) => (
+
))
) : (
diff --git a/plugins/pagerduty/src/components/Incidents.tsx b/plugins/pagerduty/src/components/Incidents.tsx
index 98a26c66db..d2f6fc7128 100644
--- a/plugins/pagerduty/src/components/Incidents.tsx
+++ b/plugins/pagerduty/src/components/Incidents.tsx
@@ -80,8 +80,9 @@ const IncidentList = ({ incidents }: IncidentListProps) => {
primary={incident.title}
secondary={
- Created {moment(incident.createdAt).fromNow()}, assigned to{' '}
- {(incident.assignees.length && incident.assignees[0].name) ||
+ Created {moment(incident.created_at).fromNow()}, assigned to{' '}
+ {(incident?.assignments[0]?.assignee?.summary &&
+ incident.assignments[0].assignee.summary) ||
'nobody'}
}
diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
index b7fde8eae3..e17e35a996 100644
--- a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
+++ b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
@@ -14,19 +14,18 @@
* limitations under the License.
*/
import React from 'react';
-import {
- InfoCard,
- MissingAnnotationEmptyState,
- useApi,
- configApiRef,
-} from '@backstage/core';
+import { InfoCard, useApi, configApiRef } from '@backstage/core';
import { Entity } from '@backstage/catalog-model';
-import { Grid } from '@material-ui/core';
+import { Grid, LinearProgress } from '@material-ui/core';
import { Incidents } from './Incidents';
import { EscalationPolicy } from './Escalation';
-import { PagerDutyData } from './types';
import { TriggerButton } from './TriggerButton';
import { useAsync } from 'react-use';
+import {
+ getServiceByIntegrationKey,
+ getIncidentsByServiceId,
+ getOncallByPolicyId,
+} from '../api/pagerDutyClient';
export const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key';
@@ -40,65 +39,63 @@ type Props = {
export const PagerDutyServiceCard = ({ entity }: Props) => {
const configApi = useApi(configApiRef);
- const { value, loading, error } = useAsync(async () => {
- const response = await fetch(
- `${configApi.getString('backend.baseUrl')}/api/pagerduty/services`,
- );
- return await response.json();
- });
- if (value) console.log(value);
- if (error) throw new Error(`Error in getting services, ${error}`);
+ // TODO: handle missing token
+ const token = configApi.getOptionalString('pagerduty.api.token') ?? undefined;
+ console.log({ token });
- // TODO: fetch this data
- const mockData: PagerDutyData = {
- pagerDutyServices: [
- {
- activeIncidents: [
- {
- id: 'id',
- title: 'something happened',
- status: 'triggered',
- createdAt: '2020:01:01',
- homepageUrl: 'url',
- assignees: [
- {
- name: 'name',
- },
- ],
- },
- ],
- escalationPolicy: [
- {
- email: 'user@example.com',
- name: 'Name',
- homepageUrl: 'https://spotify.pagerduty.com/users/FOO',
- id: 'user id',
- },
- ],
- id: 'id',
- name: 'name',
- homepageUrl: 'https://spotify.pagety.com/service-directory/BAR',
- },
- ],
- };
+ 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,
+ );
+
+ return {
+ pagerDutyServices: [
+ {
+ activeIncidents: incidents,
+ escalationPolicy: 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}`);
+ }
+
+ if (loading) {
+ return ;
+ }
const {
activeIncidents,
escalationPolicy,
homepageUrl,
- } = mockData.pagerDutyServices[0];
+ } = value!.pagerDutyServices[0]!;
const link = {
title: 'View in PagerDuty',
link: homepageUrl,
};
- return !isPluginApplicableToEntity(entity) ? (
-
- ) : (
+ return (
-
-
+
+
From 122410f1ab388d0bd48480d9d486c2b4ecd89212 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Thu, 5 Nov 2020 10:49:11 +0100
Subject: [PATCH 12/52] fix typings
---
.../pagerduty/src/components/Escalation.tsx | 12 +--
.../pagerduty/src/components/Incidents.tsx | 15 +--
plugins/pagerduty/src/components/types.tsx | 92 +++++++++++++++----
3 files changed, 88 insertions(+), 31 deletions(-)
diff --git a/plugins/pagerduty/src/components/Escalation.tsx b/plugins/pagerduty/src/components/Escalation.tsx
index 69a4543c4b..19c6a15096 100644
--- a/plugins/pagerduty/src/components/Escalation.tsx
+++ b/plugins/pagerduty/src/components/Escalation.tsx
@@ -30,7 +30,7 @@ import UserIcon from '@material-ui/icons/Person';
import EmailIcon from '@material-ui/icons/Email';
import { StatusWarning } from '@backstage/core';
import Pagerduty from '../assets/pd.svg';
-import { PagerDutyUserData } from './types';
+import { Oncall, PagerDutyEscalationPolicy } from './types';
const useStyles = makeStyles({
denseListIcon: {
@@ -45,11 +45,7 @@ const useStyles = makeStyles({
},
});
-type EscalationUserProps = {
- user: PagerDutyUserData;
-};
-
-const EscalationUser = ({ user }: EscalationUserProps) => {
+const EscalationUser = ({ user }: PagerDutyEscalationPolicy) => {
const classes = useStyles();
return (
@@ -65,7 +61,7 @@ const EscalationUser = ({ user }: EscalationUserProps) => {
@@ -96,7 +92,7 @@ const EscalationUsersEmptyState = () => {
};
type EscalationPolicyProps = {
- escalation: PagerDutyUserData[];
+ escalation: Oncall[];
};
export const EscalationPolicy = ({ escalation }: EscalationPolicyProps) => (
diff --git a/plugins/pagerduty/src/components/Incidents.tsx b/plugins/pagerduty/src/components/Incidents.tsx
index d2f6fc7128..0117281a0e 100644
--- a/plugins/pagerduty/src/components/Incidents.tsx
+++ b/plugins/pagerduty/src/components/Incidents.tsx
@@ -29,6 +29,7 @@ import {
import { StatusError, StatusWarning, StatusOK } from '@backstage/core';
import Pagerduty from '../assets/pd.svg';
import moment from 'moment';
+import { Incident } from '../components/types';
const useStyles = makeStyles({
denseListIcon: {
@@ -57,13 +58,13 @@ const IncidentsEmptyState = () => {
);
};
-type IncidentListProps = {
- incidents: any;
+type IncidentListItemProps = {
+ incident: Incident;
};
-const IncidentList = ({ incidents }: IncidentListProps) => {
+const IncidentListItem = ({ incident }: IncidentListItemProps) => {
const classes = useStyles();
- return incidents.map((incident: any) => (
+ return (
@@ -103,17 +104,17 @@ const IncidentList = ({ incidents }: IncidentListProps) => {
- ));
+ );
};
type IncidentsProps = {
- incidents: Array;
+ incidents: Incident[];
};
export const Incidents = ({ incidents }: IncidentsProps) => (
Incidents}>
{incidents.length ? (
-
+ incidents.map(incident => )
) : (
)}
diff --git a/plugins/pagerduty/src/components/types.tsx b/plugins/pagerduty/src/components/types.tsx
index b3b94f0200..377ae6d0ea 100644
--- a/plugins/pagerduty/src/components/types.tsx
+++ b/plugins/pagerduty/src/components/types.tsx
@@ -1,27 +1,87 @@
-export type PagerDutyIncident = {
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 type Incident = {
id: string;
title: string;
status: string;
- createdAt: string;
homepageUrl: string;
- assignees: Partial[];
+ assignments: [
+ {
+ assignee: User;
+ },
+ ];
+ serviceId: string;
+ created_at: string;
};
-export type PagerDutyUserData = {
+export type Service = {
+ id: string;
+ name: string;
+ html_url: string;
+ integrationKey: string;
+ escalation_policy: PagerDutyEscalationPolicy;
+};
+
+export type ResponseService = {
+ services: Service[];
+ more: boolean;
+};
+
+export type ResponseOncall = {
+ id: string;
+ name: string;
email: string;
- name: string;
- homepageUrl: string;
- id: string;
-};
-
-export type PagerDutyServicesData = {
- activeIncidents: PagerDutyIncident[];
- escalationPolicy: any; // PagerDutyUserData[];
- id: string;
- name: string;
homepageUrl: string;
};
-export type PagerDutyData = {
- pagerDutyServices: PagerDutyServicesData[];
+export type Options = {
+ method: string;
+ headers: HeadersInit;
+ body?: BodyInit;
+};
+
+export type PagerDutyClientConfig = {
+ token?: string;
+};
+
+export type ServicesResponse = {
+ services: Service[];
+};
+
+export type IncidentResponse = {
+ incidents: Incident[];
+};
+
+export type OncallsResponse = {
+ oncalls: Oncall[];
+};
+
+export type PagerDutyEscalationPolicy = {
+ user: User;
+};
+
+export type User = {
+ id: string;
+ summary: string;
+ email: string;
+ html_url: string;
+ name: string;
+};
+
+export type Oncall = {
+ escalation_policy: PagerDutyEscalationPolicy;
+ user: User;
};
From f7614ee6fce099c4e50403f979fdcbf38956193a Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Thu, 5 Nov 2020 11:17:19 +0100
Subject: [PATCH 13/52] 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'),
+ }),
+ }),
+ ],
});
From 2aed296e459dde1703a8376c6118b3c499c53686 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Wed, 11 Nov 2020 14:24:40 +0100
Subject: [PATCH 14/52] change UI bsed on UX
---
.../pagerduty/src/components/Escalation.tsx | 15 +-
.../pagerduty/src/components/Incidents.tsx | 18 +--
.../src/components/PagerDutyServiceCard.tsx | 32 +++--
.../src/components/PagerdutyCard.tsx | 132 ++++++++++++++++++
plugins/pagerduty/src/components/Pd.tsx | 27 ++++
.../src/components/TriggerButton.tsx | 24 +++-
.../src/components/TriggerDialog.tsx | 2 +
plugins/pagerduty/src/components/types.tsx | 7 +-
8 files changed, 217 insertions(+), 40 deletions(-)
create mode 100644 plugins/pagerduty/src/components/PagerdutyCard.tsx
create mode 100644 plugins/pagerduty/src/components/Pd.tsx
diff --git a/plugins/pagerduty/src/components/Escalation.tsx b/plugins/pagerduty/src/components/Escalation.tsx
index 19c6a15096..f53f79af41 100644
--- a/plugins/pagerduty/src/components/Escalation.tsx
+++ b/plugins/pagerduty/src/components/Escalation.tsx
@@ -29,8 +29,8 @@ import {
import UserIcon from '@material-ui/icons/Person';
import EmailIcon from '@material-ui/icons/Email';
import { StatusWarning } from '@backstage/core';
-import Pagerduty from '../assets/pd.svg';
-import { Oncall, PagerDutyEscalationPolicy } from './types';
+import { Oncall } from './types';
+import PagerdutyIcon from './Pd';
const useStyles = makeStyles({
denseListIcon: {
@@ -45,7 +45,7 @@ const useStyles = makeStyles({
},
});
-const EscalationUser = ({ user }: PagerDutyEscalationPolicy) => {
+const EscalationUser = ({ user }: Oncall) => {
const classes = useStyles();
return (
@@ -56,7 +56,7 @@ const EscalationUser = ({ user }: PagerDutyEscalationPolicy) => {
-
+
@@ -64,12 +64,9 @@ const EscalationUser = ({ user }: PagerDutyEscalationPolicy) => {
href={user.html_url}
target="_blank"
rel="noopener noreferrer"
+ color="primary"
>
-
+
diff --git a/plugins/pagerduty/src/components/Incidents.tsx b/plugins/pagerduty/src/components/Incidents.tsx
index dd3301904d..512836422c 100644
--- a/plugins/pagerduty/src/components/Incidents.tsx
+++ b/plugins/pagerduty/src/components/Incidents.tsx
@@ -25,11 +25,12 @@ import {
makeStyles,
IconButton,
ListSubheader,
+ Link,
} from '@material-ui/core';
import { StatusError, StatusWarning, StatusOK } from '@backstage/core';
-import Pagerduty from '../assets/pd.svg';
import moment from 'moment';
import { Incident } from '../components/types';
+import PagerdutyIcon from './Pd';
const useStyles = makeStyles({
denseListIcon: {
@@ -64,6 +65,7 @@ type IncidentListItemProps = {
const IncidentListItem = ({ incident }: IncidentListItemProps) => {
const classes = useStyles();
+ const user = incident.assignments[0].assignee;
return (
@@ -82,8 +84,9 @@ const IncidentListItem = ({ incident }: IncidentListItemProps) => {
secondary={
Created {moment(incident.created_at).fromNow()}, assigned to{' '}
- {(incident?.assignments[0]?.assignee?.summary &&
- incident.assignments[0].assignee.summary) ||
+ {(incident?.assignments[0]?.assignee?.summary && (
+ {user.summary}
+ )) ||
'nobody'}
}
@@ -91,15 +94,12 @@ const IncidentListItem = ({ incident }: IncidentListItemProps) => {
-
+
diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
index 54713b8ecd..4fd4691458 100644
--- a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
+++ b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
@@ -14,15 +14,16 @@
* limitations under the License.
*/
import React from 'react';
-import { InfoCard, useApi } from '@backstage/core';
+import { useApi } from '@backstage/core';
import { Entity } from '@backstage/catalog-model';
-import { Grid, LinearProgress } from '@material-ui/core';
+import { LinearProgress } from '@material-ui/core';
import { Incidents } from './Incidents';
import { EscalationPolicy } from './Escalation';
import { TriggerButton } from './TriggerButton';
import { useAsync } from 'react-use';
import { Alert } from '@material-ui/lab';
import { pagerDutyApiRef } from '../api/pagerDutyClient';
+import { PagerdutyCard } from './PagerdutyCard';
export const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key';
@@ -71,19 +72,26 @@ export const PagerDutyServiceCard = ({ entity }: Props) => {
return ;
}
- const link = {
+ const pagerdutyLink = {
title: 'View in PagerDuty',
- link: value!.homepageUrl,
+ href: value!.homepageUrl,
+ };
+
+ const triggerAlarm = {
+ title: 'Trigger Alarm',
+ action: ,
};
return (
-
-
-
-
-
-
- {/* todo show something when we dont have token */}
-
+
+
+
+ >
+ }
+ >
);
};
diff --git a/plugins/pagerduty/src/components/PagerdutyCard.tsx b/plugins/pagerduty/src/components/PagerdutyCard.tsx
new file mode 100644
index 0000000000..2af4d2e4bc
--- /dev/null
+++ b/plugins/pagerduty/src/components/PagerdutyCard.tsx
@@ -0,0 +1,132 @@
+import React from 'react';
+import {
+ Card,
+ CardHeader,
+ CardContent,
+ Divider,
+ Link,
+ makeStyles,
+} from '@material-ui/core';
+import LinkIcon from '@material-ui/icons/Link';
+import ReportProblemIcon from '@material-ui/icons/ReportProblem';
+import classnames from 'classnames';
+import Pagerduty from '../assets/pd.svg';
+import PagerdutyIcon from './Pd';
+
+// TODO: create a general component to use it in pagerduty and aboutCard
+
+const useStyles = makeStyles(theme => ({
+ links: {
+ margin: theme.spacing(2, 0),
+ display: 'grid',
+ gridAutoFlow: 'column',
+ gridAutoColumns: 'min-content',
+ gridGap: theme.spacing(3),
+ },
+ link: {
+ display: 'grid',
+ justifyItems: 'center',
+ gridGap: 4,
+ textAlign: 'center',
+ },
+ label: {
+ fontSize: '0.7rem',
+ textTransform: 'uppercase',
+ fontWeight: 600,
+ letterSpacing: 1.2,
+ },
+ trigger: {
+ color: theme.palette.secondary.main,
+ },
+ svgButtonImage: {
+ height: '1.5em',
+ color: theme.palette.primary.main,
+ },
+}));
+
+type SubHeaderProps = {
+ ViewPagerduty: { title: string; href: string };
+ TriggerAlarm: { title: string; action: React.ReactNode };
+};
+
+type PagerdutyCardProps = {
+ title: string;
+ subheader: SubHeaderProps;
+ content: React.ReactNode;
+};
+
+type VerticalIconProps = {
+ label: string;
+ href?: string;
+ action?: React.ReactNode;
+ icon?: React.ReactNode;
+};
+
+const VerticalIcon = ({
+ label,
+ href,
+ icon = ,
+ action,
+}: VerticalIconProps) => {
+ const classes = useStyles();
+ if (action) {
+ return (
+ <>
+
+ {icon}
+ {action}
+
+ >
+ );
+ }
+ return (
+ <>
+
+ {icon}
+ {label}
+
+ >
+ );
+};
+export const PagerdutyCard = ({
+ title,
+ subheader,
+ content,
+}: PagerdutyCardProps) => {
+ const classes = useStyles();
+ const getSubheader = ({ ViewPagerduty, TriggerAlarm }: SubHeaderProps) => {
+ return (
+
+
+ //
+
+ }
+ >
+ }
+ action={TriggerAlarm.action}
+ >
+
+ );
+ };
+
+ return (
+
+
+
+ {content}
+
+ );
+};
diff --git a/plugins/pagerduty/src/components/Pd.tsx b/plugins/pagerduty/src/components/Pd.tsx
new file mode 100644
index 0000000000..3698d0a1c4
--- /dev/null
+++ b/plugins/pagerduty/src/components/Pd.tsx
@@ -0,0 +1,27 @@
+// import React from 'react';
+// import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon';
+
+// const SvgPd = (props: SvgIconProps) =>
+// React.createElement(
+// SvgIcon,
+// props,
+//
+//
+// ,
+// );
+
+// export default SvgPd;
+
+import React from 'react';
+import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon';
+
+const SvgPd = (props: SvgIconProps) =>
+ React.createElement(
+ SvgIcon,
+ props,
+
+
+ ,
+ );
+
+export default SvgPd;
diff --git a/plugins/pagerduty/src/components/TriggerButton.tsx b/plugins/pagerduty/src/components/TriggerButton.tsx
index eb051e68fa..48bed68e2e 100644
--- a/plugins/pagerduty/src/components/TriggerButton.tsx
+++ b/plugins/pagerduty/src/components/TriggerButton.tsx
@@ -15,17 +15,34 @@
*/
import React, { useState } from 'react';
-import { Button } from '@material-ui/core';
+import { Button, makeStyles } from '@material-ui/core';
import { TriggerDialog } from './TriggerDialog';
import { Entity } from '@backstage/catalog-model';
import { PAGERDUTY_INTEGRATION_KEY } from './PagerDutyServiceCard';
+const useStyles = makeStyles({
+ triggerAlarm: {
+ paddingTop: 0,
+ paddingBottom: 0,
+ fontSize: '0.7rem',
+ textTransform: 'uppercase',
+ fontWeight: 600,
+ letterSpacing: 1.2,
+ lineHeight: 1.1,
+ '&:hover, &:focus, &.focus': {
+ backgroundColor: 'transparent',
+ textDecoration: 'none',
+ },
+ },
+});
+
type Props = {
entity: Entity;
};
export const TriggerButton = ({ entity }: Props) => {
const [showDialog, setShowDialog] = useState(false);
+ const classes = useStyles();
const handleDialog = () => {
setShowDialog(!showDialog);
@@ -34,12 +51,11 @@ export const TriggerButton = ({ entity }: Props) => {
return (
<>
- Trigger alarm
+ Trigger Alarm
{showDialog && (
{
the issue and reach out to you if necessary.
{
Date: Wed, 11 Nov 2020 14:25:59 +0100
Subject: [PATCH 15/52] add test
---
.../src/components/Escalation.test.tsx | 34 ++++++
.../src/components/Incidents.test.tsx | 71 ++++++++++++
.../src/components/TriggerDialog.test.tsx | 101 ++++++++++++++++++
3 files changed, 206 insertions(+)
create mode 100644 plugins/pagerduty/src/components/Escalation.test.tsx
create mode 100644 plugins/pagerduty/src/components/Incidents.test.tsx
create mode 100644 plugins/pagerduty/src/components/TriggerDialog.test.tsx
diff --git a/plugins/pagerduty/src/components/Escalation.test.tsx b/plugins/pagerduty/src/components/Escalation.test.tsx
new file mode 100644
index 0000000000..db52e36f4a
--- /dev/null
+++ b/plugins/pagerduty/src/components/Escalation.test.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import { EscalationPolicy } from './Escalation';
+import { wrapInTestApp } from '@backstage/test-utils';
+import { Oncall } from './types';
+
+export const escalations: Oncall[] = [
+ {
+ user: {
+ name: 'person1',
+ id: 'p1',
+ summary: 'person1',
+ email: 'person1@example.com',
+ html_url: 'http://a.com/id1',
+ },
+ },
+];
+
+describe('Escalation', () => {
+ it('render emptyState', () => {
+ const { getByText } = render(
+ wrapInTestApp( ),
+ );
+ expect(getByText('Empty escalation policy')).toBeInTheDocument();
+ });
+
+ it('render Escalation list', () => {
+ const { getByText } = render(
+ wrapInTestApp( ),
+ );
+ expect(getByText('person1')).toBeInTheDocument();
+ expect(getByText('person1@example.com')).toBeInTheDocument();
+ });
+});
diff --git a/plugins/pagerduty/src/components/Incidents.test.tsx b/plugins/pagerduty/src/components/Incidents.test.tsx
new file mode 100644
index 0000000000..62c8151af2
--- /dev/null
+++ b/plugins/pagerduty/src/components/Incidents.test.tsx
@@ -0,0 +1,71 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import { Incidents } from './Incidents';
+import { wrapInTestApp } from '@backstage/test-utils';
+import { Incident } from './types';
+
+export const incidents: Incident[] = [
+ {
+ id: 'id1',
+ status: 'triggered',
+ title: 'title1',
+ created_at: '2020-11-06T00:00:00Z',
+ assignments: [
+ {
+ assignee: {
+ name: 'person1',
+ id: 'p1',
+ summary: 'person1',
+ email: 'person1@example.com',
+ html_url: 'http://a.com/id1',
+ },
+ },
+ ],
+ homepageUrl: 'http://a.com/id1',
+ serviceId: 'sId1',
+ },
+ {
+ id: 'id2',
+ status: 'acknowledged',
+ title: 'title2',
+ created_at: '2020-11-07T00:00:00Z',
+ assignments: [
+ {
+ assignee: {
+ name: 'person2',
+ id: 'p2',
+ summary: 'person2',
+ email: 'person2@example.com',
+ html_url: 'http://a.com/id2',
+ },
+ },
+ ],
+ homepageUrl: 'http://a.com/id2',
+ serviceId: 'sId2',
+ },
+];
+
+describe('Incidents', () => {
+ it('renders an empty state is there are no incidents', () => {
+ const { getByText } = render(wrapInTestApp( ));
+ expect(getByText('No incidents')).toBeInTheDocument();
+ });
+
+ it('renders all incidents', () => {
+ const { getByText, getByTitle, getAllByTitle, getByLabelText } = render(
+ wrapInTestApp( ),
+ );
+
+ expect(getByText('title1')).toBeInTheDocument();
+ expect(getByText('title2')).toBeInTheDocument();
+ expect(getByText('person1')).toBeInTheDocument();
+ expect(getByText('person2')).toBeInTheDocument();
+ expect(getByTitle('triggered')).toBeInTheDocument();
+ expect(getByTitle('acknowledged')).toBeInTheDocument();
+ expect(getByLabelText('Status error')).toBeInTheDocument();
+ expect(getByLabelText('Status warning')).toBeInTheDocument();
+
+ // assert links, mailto and hrefs, date calculation
+ expect(getAllByTitle('View in PagerDuty').length).toEqual(2);
+ });
+});
diff --git a/plugins/pagerduty/src/components/TriggerDialog.test.tsx b/plugins/pagerduty/src/components/TriggerDialog.test.tsx
new file mode 100644
index 0000000000..a99e2c03f4
--- /dev/null
+++ b/plugins/pagerduty/src/components/TriggerDialog.test.tsx
@@ -0,0 +1,101 @@
+import React from 'react';
+import { render, fireEvent, getByTestId } from '@testing-library/react';
+import { wrapInTestApp } from '@backstage/test-utils';
+import { TriggerDialog } from './TriggerDialog';
+import {
+ ApiRegistry,
+ alertApiRef,
+ createApiRef,
+ ApiProvider,
+ IdentityApi,
+ identityApiRef,
+} from '@backstage/core';
+import { pagerDutyApiRef } from '../api/pagerDutyClient';
+import { TriggerButton } from './TriggerButton';
+import { Entity } from '@backstage/catalog-model';
+import { act } from 'react-dom/test-utils';
+
+describe('TriggerDialog', () => {
+ const mockIdentityApi: Partial = {
+ getUserId: () => 'guest@example.com',
+ };
+
+ const mockTriggerAlarmFn = jest.fn();
+ const mockPagerDutyApi = {
+ triggerPagerDutyAlarm: mockTriggerAlarmFn,
+ };
+
+ const apis = ApiRegistry.from([
+ [
+ alertApiRef,
+ createApiRef({
+ id: 'core.alert',
+ description: 'Used to report alerts and forward them to the app',
+ }),
+ ],
+ [identityApiRef, mockIdentityApi],
+ [pagerDutyApiRef, mockPagerDutyApi],
+ ]);
+
+ it('open the dialog and trigger an alarm', async () => {
+ const onClick = jest.fn(async () => {});
+
+ const entity: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: {
+ name: 'pagerduty-test',
+ annotations: {
+ 'pagerduty.com/integration-key': 'abc123',
+ },
+ },
+ };
+
+ const {
+ getByText,
+ getByRole,
+ queryByText,
+ queryByRole,
+ getByTestId,
+ } = render(
+ wrapInTestApp(
+
+
+ {/* */}
+ ,
+ ),
+ );
+ expect(queryByRole('dialog')).toBeNull();
+ const alarmButton = getByText('Trigger Alarm');
+ fireEvent.click(alarmButton);
+ expect(getByRole('dialog')).toBeInTheDocument();
+ expect(
+ getByText(
+ 'This action will send PagerDuty alarms and notifications to on-call people responsible for',
+ {
+ exact: false,
+ },
+ ),
+ ).toBeInTheDocument();
+ const input = getByTestId('trigger-input');
+ const description = 'Test Trigger Alarm';
+ await act(async () => {
+ fireEvent.change(input, { target: { value: description } });
+ });
+ const triggerButton = getByTestId('trigger-button');
+ await act(async () => {
+ fireEvent.click(triggerButton);
+ });
+ expect(mockTriggerAlarmFn).toHaveBeenCalled();
+ expect(mockTriggerAlarmFn).toHaveBeenCalledWith(
+ entity!.metadata!.annotations!['pagerduty.com/integration-key'],
+ window.location.toString(),
+ description,
+ 'guest@example.com',
+ );
+ });
+});
From 2be408a226e6ba7924057c0294b29fae4cb97406 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Wed, 11 Nov 2020 16:48:44 +0100
Subject: [PATCH 16/52] recator UI and remove comments
---
plugins/pagerduty/src/api/pagerDutyClient.ts | 12 ++++-----
plugins/pagerduty/src/assets/emptystate.svg | 26 +++++++++++++++++++
.../src/components/Escalation.test.tsx | 4 +--
.../pagerduty/src/components/Escalation.tsx | 25 +++++++++++++-----
.../pagerduty/src/components/Incidents.tsx | 19 +++++++++++---
.../src/components/PagerDutyServiceCard.tsx | 2 +-
.../src/components/PagerdutyCard.tsx | 11 +-------
plugins/pagerduty/src/components/Pd.tsx | 14 ----------
plugins/pagerduty/src/components/types.tsx | 10 +++----
9 files changed, 74 insertions(+), 49 deletions(-)
create mode 100644 plugins/pagerduty/src/assets/emptystate.svg
diff --git a/plugins/pagerduty/src/api/pagerDutyClient.ts b/plugins/pagerduty/src/api/pagerDutyClient.ts
index 09abaf856c..d0cb3719d6 100644
--- a/plugins/pagerduty/src/api/pagerDutyClient.ts
+++ b/plugins/pagerduty/src/api/pagerDutyClient.ts
@@ -22,8 +22,8 @@ import {
PagerDutyClientConfig,
ServicesResponse,
IncidentResponse,
- OncallsResponse,
- Oncall,
+ OnCallsResponse,
+ OnCall,
} from '../components/types';
export const pagerDutyApiRef = createApiRef({
@@ -34,7 +34,7 @@ export const pagerDutyApiRef = createApiRef({
interface PagerDutyClient {
getServiceByIntegrationKey(integrationKey: string): Promise;
getIncidentsByServiceId(serviceId: string): Promise;
- getOncallByPolicyId(policyId: string): Promise;
+ getOnCallByPolicyId(policyId: string): Promise;
}
export class PagerDutyClientApi implements PagerDutyClient {
@@ -67,16 +67,16 @@ export class PagerDutyClientApi implements PagerDutyClient {
return incidents;
}
- async getOncallByPolicyId(policyId: string): Promise {
+ async getOnCallByPolicyId(policyId: string): Promise {
if (!this.config?.token) {
throw new Error('Missing token');
}
const params = `include[]=users&escalation_policy_ids[]=${policyId}`;
const url = `${this.API_URL}/oncalls?${params}`;
- const { oncalls } = await this.getByUrl(url);
+ const { onCalls } = await this.getByUrl(url);
- return oncalls;
+ return onCalls;
}
triggerPagerDutyAlarm(
diff --git a/plugins/pagerduty/src/assets/emptystate.svg b/plugins/pagerduty/src/assets/emptystate.svg
new file mode 100644
index 0000000000..57b35238dd
--- /dev/null
+++ b/plugins/pagerduty/src/assets/emptystate.svg
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/pagerduty/src/components/Escalation.test.tsx b/plugins/pagerduty/src/components/Escalation.test.tsx
index db52e36f4a..13e2687b04 100644
--- a/plugins/pagerduty/src/components/Escalation.test.tsx
+++ b/plugins/pagerduty/src/components/Escalation.test.tsx
@@ -2,9 +2,9 @@ import React from 'react';
import { render } from '@testing-library/react';
import { EscalationPolicy } from './Escalation';
import { wrapInTestApp } from '@backstage/test-utils';
-import { Oncall } from './types';
+import { OnCall } from './types';
-export const escalations: Oncall[] = [
+export const escalations: OnCall[] = [
{
user: {
name: 'person1',
diff --git a/plugins/pagerduty/src/components/Escalation.tsx b/plugins/pagerduty/src/components/Escalation.tsx
index f53f79af41..56c2c27a03 100644
--- a/plugins/pagerduty/src/components/Escalation.tsx
+++ b/plugins/pagerduty/src/components/Escalation.tsx
@@ -25,11 +25,12 @@ import {
ListItemText,
makeStyles,
IconButton,
+ Typography,
} from '@material-ui/core';
-import UserIcon from '@material-ui/icons/Person';
+import Avatar from '@material-ui/core/Avatar';
import EmailIcon from '@material-ui/icons/Email';
import { StatusWarning } from '@backstage/core';
-import { Oncall } from './types';
+import { OnCall } from './types';
import PagerdutyIcon from './Pd';
const useStyles = makeStyles({
@@ -43,16 +44,26 @@ const useStyles = makeStyles({
svgButtonImage: {
height: '1em',
},
+ listItemPrimary: {
+ fontWeight: 'bold',
+ },
});
-const EscalationUser = ({ user }: Oncall) => {
+const EscalationUser = ({ user }: OnCall) => {
const classes = useStyles();
return (
-
+
-
+
+ {user.name}
+
+ }
+ secondary={user.email}
+ />
@@ -89,11 +100,11 @@ const EscalationUsersEmptyState = () => {
};
type EscalationPolicyProps = {
- escalation: Oncall[];
+ escalation: OnCall[];
};
export const EscalationPolicy = ({ escalation }: EscalationPolicyProps) => (
- Escalation Policy}>
+ ON CALL}>
{escalation.length ? (
escalation.map((item, index) => (
diff --git a/plugins/pagerduty/src/components/Incidents.tsx b/plugins/pagerduty/src/components/Incidents.tsx
index 512836422c..52247b8f06 100644
--- a/plugins/pagerduty/src/components/Incidents.tsx
+++ b/plugins/pagerduty/src/components/Incidents.tsx
@@ -26,6 +26,7 @@ import {
IconButton,
ListSubheader,
Link,
+ Typography,
} from '@material-ui/core';
import { StatusError, StatusWarning, StatusOK } from '@backstage/core';
import moment from 'moment';
@@ -43,6 +44,12 @@ const useStyles = makeStyles({
svgButtonImage: {
height: '1em',
},
+ listItemPrimary: {
+ fontWeight: 'bold',
+ },
+ listItemIcon: {
+ minWidth: '1em',
+ },
});
const IncidentsEmptyState = () => {
@@ -67,8 +74,8 @@ const IncidentListItem = ({ incident }: IncidentListItemProps) => {
const classes = useStyles();
const user = incident.assignments[0].assignee;
return (
-
-
+
+
{incident.status === 'triggered' ? (
@@ -80,7 +87,11 @@ const IncidentListItem = ({ incident }: IncidentListItemProps) => {
+ {incident.title}
+
+ }
secondary={
Created {moment(incident.created_at).fromNow()}, assigned to{' '}
@@ -112,7 +123,7 @@ type IncidentsProps = {
};
export const Incidents = ({ incidents }: IncidentsProps) => (
- Incidents}>
+ INCIDENTS}>
{incidents.length ? (
incidents.map((incident, index) => (
diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
index 4fd4691458..5bb7c9b5a9 100644
--- a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
+++ b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
@@ -47,7 +47,7 @@ export const PagerDutyServiceCard = ({ entity }: Props) => {
const service = services[0];
const incidents = await api.getIncidentsByServiceId(service.id);
- const oncalls = await api.getOncallByPolicyId(service.escalation_policy.id);
+ const oncalls = await api.getOnCallByPolicyId(service.escalation_policy.id);
return {
incidents,
diff --git a/plugins/pagerduty/src/components/PagerdutyCard.tsx b/plugins/pagerduty/src/components/PagerdutyCard.tsx
index 2af4d2e4bc..539bf69540 100644
--- a/plugins/pagerduty/src/components/PagerdutyCard.tsx
+++ b/plugins/pagerduty/src/components/PagerdutyCard.tsx
@@ -10,7 +10,6 @@ import {
import LinkIcon from '@material-ui/icons/Link';
import ReportProblemIcon from '@material-ui/icons/ReportProblem';
import classnames from 'classnames';
-import Pagerduty from '../assets/pd.svg';
import PagerdutyIcon from './Pd';
// TODO: create a general component to use it in pagerduty and aboutCard
@@ -100,15 +99,7 @@ export const PagerdutyCard = ({
- //
-
- }
+ icon={ }
>
-// React.createElement(
-// SvgIcon,
-// props,
-//
-//
-// ,
-// );
-
-// export default SvgPd;
-
import React from 'react';
import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon';
diff --git a/plugins/pagerduty/src/components/types.tsx b/plugins/pagerduty/src/components/types.tsx
index ca06e79510..dec8f18739 100644
--- a/plugins/pagerduty/src/components/types.tsx
+++ b/plugins/pagerduty/src/components/types.tsx
@@ -32,7 +32,7 @@ export type Service = {
name: string;
html_url: string;
integrationKey: string;
- escalation_policy: Oncall;
+ escalation_policy: OnCall;
};
export type ResponseService = {
@@ -40,7 +40,7 @@ export type ResponseService = {
more: boolean;
};
-export type ResponseOncall = {
+export type ResponseOnCall = {
id: string;
name: string;
email: string;
@@ -65,8 +65,8 @@ export type IncidentResponse = {
incidents: Incident[];
};
-export type OncallsResponse = {
- oncalls: Oncall[];
+export type OnCallsResponse = {
+ onCalls: OnCall[];
};
export type User = {
@@ -77,6 +77,6 @@ export type User = {
name: string;
};
-export type Oncall = {
+export type OnCall = {
user: User;
};
From ab0cceaa08777febd7cd1ae54c614b7e050ba40c Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Fri, 13 Nov 2020 11:41:06 +0100
Subject: [PATCH 17/52] refactor types
---
plugins/pagerduty/src/api/pagerDutyClient.ts | 19 +++---
.../src/components/PagerDutyServiceCard.tsx | 8 +--
plugins/pagerduty/src/components/Pd.tsx | 16 +++++
plugins/pagerduty/src/components/types.tsx | 62 ++++++++-----------
4 files changed, 58 insertions(+), 47 deletions(-)
diff --git a/plugins/pagerduty/src/api/pagerDutyClient.ts b/plugins/pagerduty/src/api/pagerDutyClient.ts
index d0cb3719d6..7b1aa442c3 100644
--- a/plugins/pagerduty/src/api/pagerDutyClient.ts
+++ b/plugins/pagerduty/src/api/pagerDutyClient.ts
@@ -18,10 +18,10 @@ import { createApiRef } from '@backstage/core';
import {
Service,
Incident,
- Options,
- PagerDutyClientConfig,
+ RequestOptions,
+ ClientApiConfig,
ServicesResponse,
- IncidentResponse,
+ IncidentsResponse,
OnCallsResponse,
OnCall,
} from '../components/types';
@@ -41,7 +41,7 @@ export class PagerDutyClientApi implements PagerDutyClient {
private API_URL = 'https://api.pagerduty.com';
private EVENTS_API_URL = 'https://events.pagerduty.com/v2';
- constructor(private readonly config?: PagerDutyClientConfig) {}
+ constructor(private readonly config?: ClientApiConfig) {}
async getServiceByIntegrationKey(integrationKey: string): Promise {
if (!this.config?.token) {
@@ -62,7 +62,7 @@ export class PagerDutyClientApi implements PagerDutyClient {
const params = `service_ids[]=${serviceId}`;
const url = `${this.API_URL}/incidents?${params}`;
- const { incidents } = await this.getByUrl(url);
+ const { incidents } = await this.getByUrl(url);
return incidents;
}
@@ -74,9 +74,9 @@ export class PagerDutyClientApi implements PagerDutyClient {
const params = `include[]=users&escalation_policy_ids[]=${policyId}`;
const url = `${this.API_URL}/oncalls?${params}`;
- const { onCalls } = await this.getByUrl(url);
+ const { oncalls } = await this.getByUrl(url);
- return onCalls;
+ return oncalls;
}
triggerPagerDutyAlarm(
@@ -127,7 +127,10 @@ export class PagerDutyClientApi implements PagerDutyClient {
return response.json();
}
- private async request(url: string, options: Options): Promise {
+ private async request(
+ url: string,
+ options: RequestOptions,
+ ): Promise {
try {
const response = await fetch(url, options);
if (!response.ok) {
diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
index 5bb7c9b5a9..d343a9fea1 100644
--- a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
+++ b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
@@ -45,13 +45,13 @@ export const PagerDutyServiceCard = ({ entity }: Props) => {
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);
+ const users = oncalls.map(item => item.user);
return {
incidents,
- oncalls,
+ users,
id: service.id,
name: service.name,
homepageUrl: service.html_url,
@@ -89,9 +89,9 @@ export const PagerDutyServiceCard = ({ entity }: Props) => {
content={
<>
-
+
>
}
- >
+ />
);
};
diff --git a/plugins/pagerduty/src/components/Pd.tsx b/plugins/pagerduty/src/components/Pd.tsx
index eeaea87a7f..36f6e1989d 100644
--- a/plugins/pagerduty/src/components/Pd.tsx
+++ b/plugins/pagerduty/src/components/Pd.tsx
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon';
diff --git a/plugins/pagerduty/src/components/types.tsx b/plugins/pagerduty/src/components/types.tsx
index dec8f18739..b229949cb4 100644
--- a/plugins/pagerduty/src/components/types.tsx
+++ b/plugins/pagerduty/src/components/types.tsx
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
export type Incident = {
id: string;
title: string;
@@ -32,41 +33,14 @@ export type Service = {
name: string;
html_url: string;
integrationKey: string;
- escalation_policy: OnCall;
+ escalation_policy: {
+ id: string;
+ user: User;
+ };
};
-export type ResponseService = {
- services: Service[];
- more: boolean;
-};
-
-export type ResponseOnCall = {
- id: string;
- name: string;
- email: string;
- homepageUrl: string;
-};
-
-export type Options = {
- method: string;
- headers: HeadersInit;
- body?: BodyInit;
-};
-
-export type PagerDutyClientConfig = {
- token?: string;
-};
-
-export type ServicesResponse = {
- services: Service[];
-};
-
-export type IncidentResponse = {
- incidents: Incident[];
-};
-
-export type OnCallsResponse = {
- onCalls: OnCall[];
+export type OnCall = {
+ user: User;
};
export type User = {
@@ -77,6 +51,24 @@ export type User = {
name: string;
};
-export type OnCall = {
- user: User;
+export type ClientApiConfig = {
+ token?: string;
+};
+
+export type ServicesResponse = {
+ services: Service[];
+};
+
+export type IncidentsResponse = {
+ incidents: Incident[];
+};
+
+export type OnCallsResponse = {
+ oncalls: OnCall[];
+};
+
+export type RequestOptions = {
+ method: string;
+ headers: HeadersInit;
+ body?: BodyInit;
};
From f9c971b3f7a8ee85e994aeb5ef79b0d5a097e3f5 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Fri, 13 Nov 2020 13:41:46 +0100
Subject: [PATCH 18/52] refactor code
---
plugins/pagerduty/package.json | 1 +
.../src/components/Escalation.test.tsx | 34 -------------
.../components/Escalation/Escalation.test.tsx | 47 +++++++++++++++++
.../Escalation/EscalationPolicy.tsx | 36 +++++++++++++
.../EscalationUser.tsx} | 49 ++----------------
.../Escalation/EscalationUsersEmptyState.tsx | 48 +++++++++++++++++
.../Incident/IncidentEmptyState.tsx | 36 +++++++++++++
.../IncidentListItem.tsx} | 51 +++----------------
.../{ => Incident}/Incidents.test.tsx | 23 +++++++--
.../src/components/Incident/Incidents.tsx | 40 +++++++++++++++
.../src/components/PagerDutyServiceCard.tsx | 4 +-
.../src/components/TriggerDialog.test.tsx | 33 ++++++------
12 files changed, 258 insertions(+), 144 deletions(-)
delete mode 100644 plugins/pagerduty/src/components/Escalation.test.tsx
create mode 100644 plugins/pagerduty/src/components/Escalation/Escalation.test.tsx
create mode 100644 plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx
rename plugins/pagerduty/src/components/{Escalation.tsx => Escalation/EscalationUser.tsx} (63%)
create mode 100644 plugins/pagerduty/src/components/Escalation/EscalationUsersEmptyState.tsx
create mode 100644 plugins/pagerduty/src/components/Incident/IncidentEmptyState.tsx
rename plugins/pagerduty/src/components/{Incidents.tsx => Incident/IncidentListItem.tsx} (72%)
rename plugins/pagerduty/src/components/{ => Incident}/Incidents.test.tsx (71%)
create mode 100644 plugins/pagerduty/src/components/Incident/Incidents.tsx
diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json
index e5d0264be3..43aad8579a 100644
--- a/plugins/pagerduty/package.json
+++ b/plugins/pagerduty/package.json
@@ -23,6 +23,7 @@
"@backstage/core": "^0.1.1-alpha.25",
"@backstage/theme": "^0.1.1-alpha.25",
"@backstage/catalog-model": "^0.1.1-alpha.25",
+ "@backstage/test-utils": "^0.1.1-alpha.25",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
diff --git a/plugins/pagerduty/src/components/Escalation.test.tsx b/plugins/pagerduty/src/components/Escalation.test.tsx
deleted file mode 100644
index 13e2687b04..0000000000
--- a/plugins/pagerduty/src/components/Escalation.test.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import React from 'react';
-import { render } from '@testing-library/react';
-import { EscalationPolicy } from './Escalation';
-import { wrapInTestApp } from '@backstage/test-utils';
-import { OnCall } from './types';
-
-export const escalations: OnCall[] = [
- {
- user: {
- name: 'person1',
- id: 'p1',
- summary: 'person1',
- email: 'person1@example.com',
- html_url: 'http://a.com/id1',
- },
- },
-];
-
-describe('Escalation', () => {
- it('render emptyState', () => {
- const { getByText } = render(
- wrapInTestApp( ),
- );
- expect(getByText('Empty escalation policy')).toBeInTheDocument();
- });
-
- it('render Escalation list', () => {
- const { getByText } = render(
- wrapInTestApp( ),
- );
- expect(getByText('person1')).toBeInTheDocument();
- expect(getByText('person1@example.com')).toBeInTheDocument();
- });
-});
diff --git a/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx b/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx
new file mode 100644
index 0000000000..3e4a59d15a
--- /dev/null
+++ b/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 } from '@testing-library/react';
+import { EscalationPolicy } from './EscalationPolicy';
+import { wrapInTestApp } from '@backstage/test-utils';
+import { User } from '../types';
+
+const escalations: User[] = [
+ {
+ name: 'person1',
+ id: 'p1',
+ summary: 'person1',
+ email: 'person1@example.com',
+ html_url: 'http://a.com/id1',
+ },
+];
+
+describe('Escalation', () => {
+ it('render emptyState', () => {
+ const { getByText } = render(
+ wrapInTestApp( ),
+ );
+ expect(getByText('Empty escalation policy')).toBeInTheDocument();
+ });
+
+ it('render Escalation list', () => {
+ const { getByText } = render(
+ wrapInTestApp( ),
+ );
+ expect(getByText('person1')).toBeInTheDocument();
+ expect(getByText('person1@example.com')).toBeInTheDocument();
+ });
+});
diff --git a/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx b/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx
new file mode 100644
index 0000000000..8a2aacead9
--- /dev/null
+++ b/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { List, ListSubheader } from '@material-ui/core';
+import { User } from '../types';
+import { EscalationUsersEmptyState } from './EscalationUsersEmptyState';
+import { EscalationUser } from './EscalationUser';
+
+type EscalationPolicyProps = {
+ users: User[];
+};
+
+export const EscalationPolicy = ({ users }: EscalationPolicyProps) => (
+ ON CALL}>
+ {users.length ? (
+ users.map((user, index) => )
+ ) : (
+ // TODO: how does it look if we used an EmptyState component here
+
+ )}
+
+);
diff --git a/plugins/pagerduty/src/components/Escalation.tsx b/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx
similarity index 63%
rename from plugins/pagerduty/src/components/Escalation.tsx
rename to plugins/pagerduty/src/components/Escalation/EscalationUser.tsx
index 56c2c27a03..3fa8ef8d65 100644
--- a/plugins/pagerduty/src/components/Escalation.tsx
+++ b/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx
@@ -16,8 +16,6 @@
import React from 'react';
import {
- List,
- ListSubheader,
ListItem,
ListItemIcon,
ListItemSecondaryAction,
@@ -29,27 +27,16 @@ import {
} from '@material-ui/core';
import Avatar from '@material-ui/core/Avatar';
import EmailIcon from '@material-ui/icons/Email';
-import { StatusWarning } from '@backstage/core';
-import { OnCall } from './types';
-import PagerdutyIcon from './Pd';
+import PagerdutyIcon from '../Pd';
+import { User } from '../types';
const useStyles = makeStyles({
- denseListIcon: {
- marginRight: 0,
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center',
- justifyContent: 'center',
- },
- svgButtonImage: {
- height: '1em',
- },
listItemPrimary: {
fontWeight: 'bold',
},
});
-const EscalationUser = ({ user }: OnCall) => {
+export const EscalationUser = ({ user }: { user: User }) => {
const classes = useStyles();
return (
@@ -84,33 +71,3 @@ const EscalationUser = ({ user }: OnCall) => {
);
};
-
-const EscalationUsersEmptyState = () => {
- const classes = useStyles();
- return (
-
-
-
-
-
-
-
-
- );
-};
-
-type EscalationPolicyProps = {
- escalation: OnCall[];
-};
-
-export const EscalationPolicy = ({ escalation }: EscalationPolicyProps) => (
- ON CALL}>
- {escalation.length ? (
- escalation.map((item, index) => (
-
- ))
- ) : (
-
- )}
-
-);
diff --git a/plugins/pagerduty/src/components/Escalation/EscalationUsersEmptyState.tsx b/plugins/pagerduty/src/components/Escalation/EscalationUsersEmptyState.tsx
new file mode 100644
index 0000000000..d587011601
--- /dev/null
+++ b/plugins/pagerduty/src/components/Escalation/EscalationUsersEmptyState.tsx
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 {
+ ListItem,
+ ListItemIcon,
+ ListItemText,
+ makeStyles,
+} from '@material-ui/core';
+import { StatusWarning } from '@backstage/core';
+
+const useStyles = makeStyles({
+ denseListIcon: {
+ marginRight: 0,
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+});
+
+export const EscalationUsersEmptyState = () => {
+ const classes = useStyles();
+ return (
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/plugins/pagerduty/src/components/Incident/IncidentEmptyState.tsx b/plugins/pagerduty/src/components/Incident/IncidentEmptyState.tsx
new file mode 100644
index 0000000000..ded161261e
--- /dev/null
+++ b/plugins/pagerduty/src/components/Incident/IncidentEmptyState.tsx
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { Grid } from '@material-ui/core';
+import EmptyStateImage from '../../assets/emptyState.svg';
+
+export const IncidentsEmptyState = () => {
+ return (
+
+
+ Nice! No incidents are assigned to you!
+
+
+
+
+
+ );
+};
diff --git a/plugins/pagerduty/src/components/Incidents.tsx b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx
similarity index 72%
rename from plugins/pagerduty/src/components/Incidents.tsx
rename to plugins/pagerduty/src/components/Incident/IncidentListItem.tsx
index 52247b8f06..f01c99fc8c 100644
--- a/plugins/pagerduty/src/components/Incidents.tsx
+++ b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx
@@ -16,7 +16,6 @@
import React from 'react';
import {
- List,
ListItem,
ListItemIcon,
ListItemSecondaryAction,
@@ -24,14 +23,17 @@ import {
ListItemText,
makeStyles,
IconButton,
- ListSubheader,
Link,
Typography,
} from '@material-ui/core';
-import { StatusError, StatusWarning, StatusOK } from '@backstage/core';
+import { StatusError, StatusWarning } from '@backstage/core';
import moment from 'moment';
-import { Incident } from '../components/types';
-import PagerdutyIcon from './Pd';
+import { Incident } from '../types';
+import PagerdutyIcon from '../Pd';
+
+type IncidentListItemProps = {
+ incident: Incident;
+};
const useStyles = makeStyles({
denseListIcon: {
@@ -41,9 +43,6 @@ const useStyles = makeStyles({
alignItems: 'center',
justifyContent: 'center',
},
- svgButtonImage: {
- height: '1em',
- },
listItemPrimary: {
fontWeight: 'bold',
},
@@ -52,25 +51,7 @@ const useStyles = makeStyles({
},
});
-const IncidentsEmptyState = () => {
- const classes = useStyles();
- return (
-
-
-
-
-
-
-
-
- );
-};
-
-type IncidentListItemProps = {
- incident: Incident;
-};
-
-const IncidentListItem = ({ incident }: IncidentListItemProps) => {
+export const IncidentListItem = ({ incident }: IncidentListItemProps) => {
const classes = useStyles();
const user = incident.assignments[0].assignee;
return (
@@ -117,19 +98,3 @@ const IncidentListItem = ({ incident }: IncidentListItemProps) => {
);
};
-
-type IncidentsProps = {
- incidents: Incident[];
-};
-
-export const Incidents = ({ incidents }: IncidentsProps) => (
- INCIDENTS}>
- {incidents.length ? (
- incidents.map((incident, index) => (
-
- ))
- ) : (
-
- )}
-
-);
diff --git a/plugins/pagerduty/src/components/Incidents.test.tsx b/plugins/pagerduty/src/components/Incident/Incidents.test.tsx
similarity index 71%
rename from plugins/pagerduty/src/components/Incidents.test.tsx
rename to plugins/pagerduty/src/components/Incident/Incidents.test.tsx
index 62c8151af2..fbe3783d30 100644
--- a/plugins/pagerduty/src/components/Incidents.test.tsx
+++ b/plugins/pagerduty/src/components/Incident/Incidents.test.tsx
@@ -1,10 +1,25 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 } from '@testing-library/react';
import { Incidents } from './Incidents';
import { wrapInTestApp } from '@backstage/test-utils';
-import { Incident } from './types';
+import { Incident } from '../types';
-export const incidents: Incident[] = [
+const incidents: Incident[] = [
{
id: 'id1',
status: 'triggered',
@@ -48,7 +63,9 @@ export const incidents: Incident[] = [
describe('Incidents', () => {
it('renders an empty state is there are no incidents', () => {
const { getByText } = render(wrapInTestApp( ));
- expect(getByText('No incidents')).toBeInTheDocument();
+ expect(
+ getByText('Nice! No incidents are assigned to you!'),
+ ).toBeInTheDocument();
});
it('renders all incidents', () => {
diff --git a/plugins/pagerduty/src/components/Incident/Incidents.tsx b/plugins/pagerduty/src/components/Incident/Incidents.tsx
new file mode 100644
index 0000000000..a49d913c80
--- /dev/null
+++ b/plugins/pagerduty/src/components/Incident/Incidents.tsx
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { List, ListSubheader } from '@material-ui/core';
+import { Incident } from '../types';
+import { IncidentListItem } from './IncidentListItem';
+import { IncidentsEmptyState } from './IncidentEmptyState';
+
+type IncidentsProps = {
+ incidents: Incident[];
+};
+
+export const Incidents = ({ incidents }: IncidentsProps) => (
+ {incidents.length && 'INCIDENTS'}}
+ >
+ {incidents.length ? (
+ incidents.map((incident, index) => (
+
+ ))
+ ) : (
+
+ )}
+
+);
diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
index d343a9fea1..480e7cdbd6 100644
--- a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
+++ b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
@@ -17,8 +17,8 @@ import React from 'react';
import { useApi } from '@backstage/core';
import { Entity } from '@backstage/catalog-model';
import { LinearProgress } from '@material-ui/core';
-import { Incidents } from './Incidents';
-import { EscalationPolicy } from './Escalation';
+import { Incidents } from './Incident/Incidents';
+import { EscalationPolicy } from './Escalation/EscalationPolicy';
import { TriggerButton } from './TriggerButton';
import { useAsync } from 'react-use';
import { Alert } from '@material-ui/lab';
diff --git a/plugins/pagerduty/src/components/TriggerDialog.test.tsx b/plugins/pagerduty/src/components/TriggerDialog.test.tsx
index a99e2c03f4..387b168e05 100644
--- a/plugins/pagerduty/src/components/TriggerDialog.test.tsx
+++ b/plugins/pagerduty/src/components/TriggerDialog.test.tsx
@@ -1,7 +1,21 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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, fireEvent, getByTestId } from '@testing-library/react';
+import { render, fireEvent } from '@testing-library/react';
import { wrapInTestApp } from '@backstage/test-utils';
-import { TriggerDialog } from './TriggerDialog';
import {
ApiRegistry,
alertApiRef,
@@ -38,8 +52,6 @@ describe('TriggerDialog', () => {
]);
it('open the dialog and trigger an alarm', async () => {
- const onClick = jest.fn(async () => {});
-
const entity: Entity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
@@ -51,21 +63,10 @@ describe('TriggerDialog', () => {
},
};
- const {
- getByText,
- getByRole,
- queryByText,
- queryByRole,
- getByTestId,
- } = render(
+ const { getByText, getByRole, queryByRole, getByTestId } = render(
wrapInTestApp(
- {/* */}
,
),
);
From 00a2ead383598e17bf5f5a9d833656520daf4a57 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Mon, 16 Nov 2020 16:09:45 +0100
Subject: [PATCH 19/52] refactor code an structure
---
.../components/Escalation/Escalation.test.tsx | 2 +-
.../Escalation/EscalationPolicy.tsx | 1 -
.../components/Escalation/EscalationUser.tsx | 2 +-
.../src/components/Escalation/index.ts | 17 ++
.../components/Incident/IncidentListItem.tsx | 2 +-
.../src/components/Incident/index.ts | 17 ++
.../components/{Pd.tsx => PagerDutyIcon.tsx} | 0
.../src/components/PagerDutyServiceCard.tsx | 97 ---------
.../src/components/PagerdutyCard.tsx | 194 ++++++++++--------
.../{ => TriggerButton}/TriggerButton.tsx | 4 +-
.../src/components/TriggerButton/index.ts | 17 ++
.../TriggerDialog.test.tsx | 4 +-
.../{ => TriggerDialog}/TriggerDialog.tsx | 2 +-
.../src/components/TriggerDialog/index.ts | 17 ++
plugins/pagerduty/src/components/types.tsx | 9 +-
15 files changed, 186 insertions(+), 199 deletions(-)
create mode 100644 plugins/pagerduty/src/components/Escalation/index.ts
create mode 100644 plugins/pagerduty/src/components/Incident/index.ts
rename plugins/pagerduty/src/components/{Pd.tsx => PagerDutyIcon.tsx} (100%)
delete mode 100644 plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
rename plugins/pagerduty/src/components/{ => TriggerButton}/TriggerButton.tsx (93%)
create mode 100644 plugins/pagerduty/src/components/TriggerButton/index.ts
rename plugins/pagerduty/src/components/{ => TriggerDialog}/TriggerDialog.test.tsx (96%)
rename plugins/pagerduty/src/components/{ => TriggerDialog}/TriggerDialog.tsx (98%)
create mode 100644 plugins/pagerduty/src/components/TriggerDialog/index.ts
diff --git a/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx b/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx
index 3e4a59d15a..4fa65eb999 100644
--- a/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx
+++ b/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx
@@ -37,7 +37,7 @@ describe('Escalation', () => {
expect(getByText('Empty escalation policy')).toBeInTheDocument();
});
- it('render Escalation list', () => {
+ it('render escalation list', () => {
const { getByText } = render(
wrapInTestApp( ),
);
diff --git a/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx b/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx
index 8a2aacead9..cbce0ce4a3 100644
--- a/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx
+++ b/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx
@@ -29,7 +29,6 @@ export const EscalationPolicy = ({ users }: EscalationPolicyProps) => (
{users.length ? (
users.map((user, index) => )
) : (
- // TODO: how does it look if we used an EmptyState component here
)}
diff --git a/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx b/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx
index 3fa8ef8d65..e20a5f7567 100644
--- a/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx
+++ b/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx
@@ -27,7 +27,7 @@ import {
} from '@material-ui/core';
import Avatar from '@material-ui/core/Avatar';
import EmailIcon from '@material-ui/icons/Email';
-import PagerdutyIcon from '../Pd';
+import PagerdutyIcon from '../PagerDutyIcon';
import { User } from '../types';
const useStyles = makeStyles({
diff --git a/plugins/pagerduty/src/components/Escalation/index.ts b/plugins/pagerduty/src/components/Escalation/index.ts
new file mode 100644
index 0000000000..ac2db62cd9
--- /dev/null
+++ b/plugins/pagerduty/src/components/Escalation/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { EscalationPolicy } from './EscalationPolicy';
diff --git a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx
index f01c99fc8c..4700583a8b 100644
--- a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx
+++ b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx
@@ -29,7 +29,7 @@ import {
import { StatusError, StatusWarning } from '@backstage/core';
import moment from 'moment';
import { Incident } from '../types';
-import PagerdutyIcon from '../Pd';
+import PagerdutyIcon from '../PagerDutyIcon';
type IncidentListItemProps = {
incident: Incident;
diff --git a/plugins/pagerduty/src/components/Incident/index.ts b/plugins/pagerduty/src/components/Incident/index.ts
new file mode 100644
index 0000000000..fb2702602b
--- /dev/null
+++ b/plugins/pagerduty/src/components/Incident/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { Incidents } from './Incidents';
diff --git a/plugins/pagerduty/src/components/Pd.tsx b/plugins/pagerduty/src/components/PagerDutyIcon.tsx
similarity index 100%
rename from plugins/pagerduty/src/components/Pd.tsx
rename to plugins/pagerduty/src/components/PagerDutyIcon.tsx
diff --git a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx b/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
deleted file mode 100644
index 480e7cdbd6..0000000000
--- a/plugins/pagerduty/src/components/PagerDutyServiceCard.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright 2020 Spotify AB
- *
- * 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 { useApi } from '@backstage/core';
-import { Entity } from '@backstage/catalog-model';
-import { LinearProgress } from '@material-ui/core';
-import { Incidents } from './Incident/Incidents';
-import { EscalationPolicy } from './Escalation/EscalationPolicy';
-import { TriggerButton } from './TriggerButton';
-import { useAsync } from 'react-use';
-import { Alert } from '@material-ui/lab';
-import { pagerDutyApiRef } from '../api/pagerDutyClient';
-import { PagerdutyCard } from './PagerdutyCard';
-
-export const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key';
-
-export const isPluginApplicableToEntity = (entity: Entity) =>
- Boolean(entity.metadata.annotations?.[PAGERDUTY_INTEGRATION_KEY]);
-
-type Props = {
- entity: Entity;
-};
-
-export const PagerDutyServiceCard = ({ entity }: Props) => {
- const api = useApi(pagerDutyApiRef);
-
- const { value, loading, error } = useAsync(async () => {
- const integrationKey = entity.metadata.annotations![
- PAGERDUTY_INTEGRATION_KEY
- ];
-
- 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);
- const users = oncalls.map(item => item.user);
-
- return {
- incidents,
- users,
- id: service.id,
- name: service.name,
- homepageUrl: service.html_url,
- };
- });
-
- if (error) {
- return (
-
-
- Error encountered while fetching information. {error.message}
-
-
- );
- }
-
- if (loading) {
- return ;
- }
-
- const pagerdutyLink = {
- title: 'View in PagerDuty',
- href: value!.homepageUrl,
- };
-
- const triggerAlarm = {
- title: 'Trigger Alarm',
- action: ,
- };
-
- return (
-
-
-
- >
- }
- />
- );
-};
diff --git a/plugins/pagerduty/src/components/PagerdutyCard.tsx b/plugins/pagerduty/src/components/PagerdutyCard.tsx
index 539bf69540..6b6d466850 100644
--- a/plugins/pagerduty/src/components/PagerdutyCard.tsx
+++ b/plugins/pagerduty/src/components/PagerdutyCard.tsx
@@ -1,18 +1,38 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { useApi, EmptyState } from '@backstage/core';
+import { Entity } from '@backstage/catalog-model';
import {
Card,
- CardHeader,
CardContent,
+ CardHeader,
Divider,
- Link,
+ LinearProgress,
makeStyles,
} from '@material-ui/core';
-import LinkIcon from '@material-ui/icons/Link';
+import { Incidents } from './Incident';
+import { EscalationPolicy } from './Escalation';
+import { TriggerButton } from './TriggerButton';
+import { useAsync } from 'react-use';
+import { Alert } from '@material-ui/lab';
+import { pagerDutyApiRef, UnauthorizedError } from '../api';
+import { IconLinkVertical } from '@backstage/plugin-catalog';
+import PagerDutyIcon from './PagerDutyIcon';
import ReportProblemIcon from '@material-ui/icons/ReportProblem';
-import classnames from 'classnames';
-import PagerdutyIcon from './Pd';
-
-// TODO: create a general component to use it in pagerduty and aboutCard
const useStyles = makeStyles(theme => ({
links: {
@@ -22,102 +42,98 @@ const useStyles = makeStyles(theme => ({
gridAutoColumns: 'min-content',
gridGap: theme.spacing(3),
},
- link: {
- display: 'grid',
- justifyItems: 'center',
- gridGap: 4,
- textAlign: 'center',
- },
- label: {
- fontSize: '0.7rem',
- textTransform: 'uppercase',
- fontWeight: 600,
- letterSpacing: 1.2,
- },
- trigger: {
- color: theme.palette.secondary.main,
- },
- svgButtonImage: {
- height: '1.5em',
- color: theme.palette.primary.main,
- },
}));
-type SubHeaderProps = {
- ViewPagerduty: { title: string; href: string };
- TriggerAlarm: { title: string; action: React.ReactNode };
+export const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key';
+
+export const isPluginApplicableToEntity = (entity: Entity) =>
+ Boolean(entity.metadata.annotations?.[PAGERDUTY_INTEGRATION_KEY]);
+
+type Props = {
+ entity: Entity;
};
-type PagerdutyCardProps = {
- title: string;
- subheader: SubHeaderProps;
- content: React.ReactNode;
-};
-
-type VerticalIconProps = {
- label: string;
- href?: string;
- action?: React.ReactNode;
- icon?: React.ReactNode;
-};
-
-const VerticalIcon = ({
- label,
- href,
- icon = ,
- action,
-}: VerticalIconProps) => {
+export const PagerDutyCard = ({ entity }: Props) => {
const classes = useStyles();
- if (action) {
+ const api = useApi(pagerDutyApiRef);
+
+ const { value, loading, error } = useAsync(async () => {
+ const integrationKey = entity.metadata.annotations![
+ PAGERDUTY_INTEGRATION_KEY
+ ];
+
+ const services = await api.getServiceByIntegrationKey(integrationKey);
+ const incidents = await api.getIncidentsByServiceId(services[0].id);
+ const oncalls = await api.getOnCallByPolicyId(
+ services[0].escalation_policy.id,
+ );
+ const users = oncalls.map(oncall => oncall.user);
+
+ return {
+ incidents,
+ users,
+ id: services[0].id,
+ name: services[0].name,
+ homepageUrl: services[0].html_url,
+ };
+ });
+
+ if (error) {
+ if (error instanceof UnauthorizedError) {
+ return (
+
+ );
+ }
+
return (
- <>
-
- {icon}
- {action}
-
- >
+
+ Error encountered while fetching information. {error.message}
+
);
}
- return (
- <>
-
- {icon}
- {label}
-
- >
- );
-};
-export const PagerdutyCard = ({
- title,
- subheader,
- content,
-}: PagerdutyCardProps) => {
- const classes = useStyles();
- const getSubheader = ({ ViewPagerduty, TriggerAlarm }: SubHeaderProps) => {
- return (
-
- }
- >
- }
- action={TriggerAlarm.action}
- >
-
- );
+
+ if (loading) {
+ return ;
+ }
+
+ const pagerdutyLink = {
+ title: 'View in PagerDuty',
+ href: value!.homepageUrl,
+ };
+
+ const triggerAlarm = {
+ title: 'Trigger Alarm',
+ action: ,
};
return (
+ title="PagerDuty"
+ subheader={
+
+ }
+ />
+ }
+ action={triggerAlarm.action}
+ />
+
+ }
+ />
- {content}
+
+
+
+
);
};
diff --git a/plugins/pagerduty/src/components/TriggerButton.tsx b/plugins/pagerduty/src/components/TriggerButton/TriggerButton.tsx
similarity index 93%
rename from plugins/pagerduty/src/components/TriggerButton.tsx
rename to plugins/pagerduty/src/components/TriggerButton/TriggerButton.tsx
index 48bed68e2e..8feebf9e89 100644
--- a/plugins/pagerduty/src/components/TriggerButton.tsx
+++ b/plugins/pagerduty/src/components/TriggerButton/TriggerButton.tsx
@@ -16,9 +16,9 @@
import React, { useState } from 'react';
import { Button, makeStyles } from '@material-ui/core';
-import { TriggerDialog } from './TriggerDialog';
+import { TriggerDialog } from '../TriggerDialog';
import { Entity } from '@backstage/catalog-model';
-import { PAGERDUTY_INTEGRATION_KEY } from './PagerDutyServiceCard';
+import { PAGERDUTY_INTEGRATION_KEY } from '../PagerDutyCard';
const useStyles = makeStyles({
triggerAlarm: {
diff --git a/plugins/pagerduty/src/components/TriggerButton/index.ts b/plugins/pagerduty/src/components/TriggerButton/index.ts
new file mode 100644
index 0000000000..6a58a4f43f
--- /dev/null
+++ b/plugins/pagerduty/src/components/TriggerButton/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { TriggerButton } from './TriggerButton';
diff --git a/plugins/pagerduty/src/components/TriggerDialog.test.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx
similarity index 96%
rename from plugins/pagerduty/src/components/TriggerDialog.test.tsx
rename to plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx
index 387b168e05..7e26985136 100644
--- a/plugins/pagerduty/src/components/TriggerDialog.test.tsx
+++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx
@@ -24,8 +24,8 @@ import {
IdentityApi,
identityApiRef,
} from '@backstage/core';
-import { pagerDutyApiRef } from '../api/pagerDutyClient';
-import { TriggerButton } from './TriggerButton';
+import { pagerDutyApiRef } from '../../api';
+import { TriggerButton } from '../TriggerButton';
import { Entity } from '@backstage/catalog-model';
import { act } from 'react-dom/test-utils';
diff --git a/plugins/pagerduty/src/components/TriggerDialog.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx
similarity index 98%
rename from plugins/pagerduty/src/components/TriggerDialog.tsx
rename to plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx
index 08044d02f6..91ec3cd27d 100644
--- a/plugins/pagerduty/src/components/TriggerDialog.tsx
+++ b/plugins/pagerduty/src/components/TriggerDialog/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 { pagerDutyApiRef } from '../api/pagerDutyClient';
+import { pagerDutyApiRef } from '../../api';
const useStyles = makeStyles({
warningText: {
diff --git a/plugins/pagerduty/src/components/TriggerDialog/index.ts b/plugins/pagerduty/src/components/TriggerDialog/index.ts
new file mode 100644
index 0000000000..655cef8504
--- /dev/null
+++ b/plugins/pagerduty/src/components/TriggerDialog/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { TriggerDialog } from './TriggerDialog';
diff --git a/plugins/pagerduty/src/components/types.tsx b/plugins/pagerduty/src/components/types.tsx
index b229949cb4..9c9e29cb01 100644
--- a/plugins/pagerduty/src/components/types.tsx
+++ b/plugins/pagerduty/src/components/types.tsx
@@ -51,10 +51,6 @@ export type User = {
name: string;
};
-export type ClientApiConfig = {
- token?: string;
-};
-
export type ServicesResponse = {
services: Service[];
};
@@ -72,3 +68,8 @@ export type RequestOptions = {
headers: HeadersInit;
body?: BodyInit;
};
+
+export type ClientApiConfig = {
+ api_url: string;
+ events_url: string;
+};
From 6fc060842ed1de91b0c173b3693e3d3f11c91f9b Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Mon, 16 Nov 2020 16:34:49 +0100
Subject: [PATCH 20/52] refactor api to use token from proxy
---
.../app/src/components/catalog/EntityPage.tsx | 4 +-
plugins/pagerduty/package.json | 1 +
.../src/api/{pagerDutyClient.ts => client.ts} | 60 +++++++------------
plugins/pagerduty/src/api/index.ts | 22 +++++++
plugins/pagerduty/src/api/types.ts | 37 ++++++++++++
plugins/pagerduty/src/index.ts | 4 +-
plugins/pagerduty/src/plugin.ts | 7 ++-
7 files changed, 91 insertions(+), 44 deletions(-)
rename plugins/pagerduty/src/api/{pagerDutyClient.ts => client.ts} (68%)
create mode 100644 plugins/pagerduty/src/api/index.ts
create mode 100644 plugins/pagerduty/src/api/types.ts
diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx
index ddc8ed4b13..4fc894ef90 100644
--- a/packages/app/src/components/catalog/EntityPage.tsx
+++ b/packages/app/src/components/catalog/EntityPage.tsx
@@ -68,7 +68,7 @@ import {
} from '@roadiehq/backstage-plugin-github-pull-requests';
import {
isPluginApplicableToEntity as isPagerDutyAvailable,
- PagerDutyServiceCard,
+ PagerDutyCard,
} from '@backstage/plugin-pagerduty';
const CICDSwitcher = ({ entity }: { entity: Entity }) => {
@@ -137,7 +137,7 @@ const OverviewContent = ({ entity }: { entity: Entity }) => (
{isPagerDutyAvailable(entity) && (
-
+
)}
diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json
index 43aad8579a..29518bd858 100644
--- a/plugins/pagerduty/package.json
+++ b/plugins/pagerduty/package.json
@@ -24,6 +24,7 @@
"@backstage/theme": "^0.1.1-alpha.25",
"@backstage/catalog-model": "^0.1.1-alpha.25",
"@backstage/test-utils": "^0.1.1-alpha.25",
+ "@backstage/plugin-catalog": "^0.1.1-alpha.25",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
diff --git a/plugins/pagerduty/src/api/pagerDutyClient.ts b/plugins/pagerduty/src/api/client.ts
similarity index 68%
rename from plugins/pagerduty/src/api/pagerDutyClient.ts
rename to plugins/pagerduty/src/api/client.ts
index 7b1aa442c3..985bb25e5a 100644
--- a/plugins/pagerduty/src/api/pagerDutyClient.ts
+++ b/plugins/pagerduty/src/api/client.ts
@@ -25,55 +25,41 @@ import {
OnCallsResponse,
OnCall,
} from '../components/types';
+import { PagerDutyClient } from './types';
+
+export class UnauthorizedError extends Error {
+ constructor() {
+ super();
+ }
+}
export const pagerDutyApiRef = createApiRef({
id: 'plugin.pagerduty.api',
description: 'Used to fetch data from PagerDuty API',
});
-interface PagerDutyClient {
- getServiceByIntegrationKey(integrationKey: string): Promise;
- getIncidentsByServiceId(serviceId: string): Promise;
- getOnCallByPolicyId(policyId: string): Promise;
-}
-
export class PagerDutyClientApi implements PagerDutyClient {
- private API_URL = 'https://api.pagerduty.com';
- private EVENTS_API_URL = 'https://events.pagerduty.com/v2';
-
- constructor(private readonly config?: ClientApiConfig) {}
+ constructor(private readonly config: ClientApiConfig) {}
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 url = `${this.config.api_url}/services?${params}`;
const { services } = await this.getByUrl(url);
return services;
}
async getIncidentsByServiceId(serviceId: string): Promise {
- if (!this.config?.token) {
- throw new Error('Missing token');
- }
-
const params = `service_ids[]=${serviceId}`;
- const url = `${this.API_URL}/incidents?${params}`;
+ const url = `${this.config.api_url}/incidents?${params}`;
const { incidents } = await this.getByUrl(url);
return incidents;
}
async getOnCallByPolicyId(policyId: string): Promise {
- if (!this.config?.token) {
- throw new Error('Missing token');
- }
-
const params = `include[]=users&escalation_policy_ids[]=${policyId}`;
- const url = `${this.API_URL}/oncalls?${params}`;
+ const url = `${this.config.api_url}/oncalls?${params}`;
const { oncalls } = await this.getByUrl(url);
return oncalls;
@@ -110,14 +96,13 @@ export class PagerDutyClientApi implements PagerDutyClient {
body,
};
- return this.request(`${this.EVENTS_API_URL}/enqueue`, options);
+ return this.request(`${this.config.events_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',
},
@@ -131,17 +116,16 @@ export class PagerDutyClientApi implements PagerDutyClient {
url: string,
options: RequestOptions,
): 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);
+ const response = await fetch(url, options);
+ if (response.status === 401) {
+ throw new UnauthorizedError();
}
+ 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;
}
}
diff --git a/plugins/pagerduty/src/api/index.ts b/plugins/pagerduty/src/api/index.ts
new file mode 100644
index 0000000000..5ac7470475
--- /dev/null
+++ b/plugins/pagerduty/src/api/index.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 {
+ PagerDutyClientApi,
+ pagerDutyApiRef,
+ UnauthorizedError,
+} from './client';
+export type { PagerDutyClient } from './types';
diff --git a/plugins/pagerduty/src/api/types.ts b/plugins/pagerduty/src/api/types.ts
new file mode 100644
index 0000000000..cb9d7fc5bc
--- /dev/null
+++ b/plugins/pagerduty/src/api/types.ts
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { Incident, OnCall, Service } from '../components/types';
+
+export interface PagerDutyClient {
+ /**
+ * Fetches a list of services, filtered by the provided integration key.
+ *
+ */
+ getServiceByIntegrationKey(integrationKey: string): Promise;
+
+ /**
+ * Fetches a list of incidents a provided service has.
+ *
+ */
+ getIncidentsByServiceId(serviceId: string): Promise;
+
+ /**
+ * Fetches the list of users in an escalation policy.
+ *
+ */
+ getOnCallByPolicyId(policyId: string): Promise;
+}
diff --git a/plugins/pagerduty/src/index.ts b/plugins/pagerduty/src/index.ts
index b24018887a..58dc39932d 100644
--- a/plugins/pagerduty/src/index.ts
+++ b/plugins/pagerduty/src/index.ts
@@ -16,5 +16,5 @@
export { plugin } from './plugin';
export {
isPluginApplicableToEntity,
- PagerDutyServiceCard,
-} from './components/PagerDutyServiceCard';
+ PagerDutyCard,
+} from './components/PagerDutyCard';
diff --git a/plugins/pagerduty/src/plugin.ts b/plugins/pagerduty/src/plugin.ts
index 4073551dc8..2e07b5c430 100644
--- a/plugins/pagerduty/src/plugin.ts
+++ b/plugins/pagerduty/src/plugin.ts
@@ -19,7 +19,7 @@ import {
createRouteRef,
configApiRef,
} from '@backstage/core';
-import { pagerDutyApiRef, PagerDutyClientApi } from './api/pagerDutyClient';
+import { pagerDutyApiRef, PagerDutyClientApi } from './api';
export const rootRouteRef = createRouteRef({
path: '/pagerduty',
@@ -34,7 +34,10 @@ export const plugin = createPlugin({
deps: { configApi: configApiRef },
factory: ({ configApi }) =>
new PagerDutyClientApi({
- token: configApi.getOptionalString('pagerduty.api.token'),
+ api_url: `${configApi.getString(
+ 'backend.baseUrl',
+ )}/api/proxy/pagerduty`,
+ events_url: 'https://events.pagerduty.com/v2',
}),
}),
],
From e1c23fa4b318a482c4dff49c62a86fc1268ccb38 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Mon, 16 Nov 2020 16:36:03 +0100
Subject: [PATCH 21/52] add action prop to support buttons
---
.../IconLinkVertical/IconLinkVertical.tsx | 26 +++++++++++++++++--
plugins/catalog/src/index.ts | 1 +
2 files changed, 25 insertions(+), 2 deletions(-)
diff --git a/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx b/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx
index 580dcff0f5..63df4feaf9 100644
--- a/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx
+++ b/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx
@@ -23,9 +23,10 @@ export type IconLinkVerticalProps = {
href?: string;
disabled?: boolean;
label: string;
+ action?: React.ReactNode;
};
-const useIconStyles = makeStyles({
+const useIconStyles = makeStyles(theme => ({
link: {
display: 'grid',
justifyItems: 'center',
@@ -41,12 +42,16 @@ const useIconStyles = makeStyles({
fontWeight: 600,
letterSpacing: 1.2,
},
-});
+ linkStyle: {
+ color: theme.palette.secondary.main,
+ },
+}));
export function IconLinkVertical({
icon = ,
href = '#',
disabled = false,
+ action,
...props
}: IconLinkVerticalProps) {
const classes = useIconStyles();
@@ -64,6 +69,23 @@ export function IconLinkVertical({
);
}
+ if (action) {
+ return (
+
+ {icon}
+ {action}
+
+ );
+ }
+
return (
{icon}
diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts
index 762f4924c0..101680c552 100644
--- a/plugins/catalog/src/index.ts
+++ b/plugins/catalog/src/index.ts
@@ -23,3 +23,4 @@ export { Router } from './components/Router';
export { useEntity, EntityContext } from './hooks/useEntity';
export { AboutCard } from './components/AboutCard';
export { EntityPageLayout } from './components/EntityPageLayout';
+export { IconLinkVertical } from './components/AboutCard/IconLinkVertical';
From 00186ab37818c2efbe3f4be7e6a782bf47755996 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Mon, 16 Nov 2020 16:50:58 +0100
Subject: [PATCH 22/52] remove pagerduty backend
---
packages/backend/package.json | 1 -
packages/backend/src/index.ts | 3 -
packages/backend/src/plugins/pagerduty.ts | 27 ---
plugins/pagerduty-backend/.eslintrc.js | 3 -
plugins/pagerduty-backend/README.md | 13 --
plugins/pagerduty-backend/package.json | 40 -----
plugins/pagerduty-backend/src/index.ts | 17 --
plugins/pagerduty-backend/src/run.ts | 33 ----
.../src/service/router.test.ts | 45 -----
.../pagerduty-backend/src/service/router.ts | 169 ------------------
.../src/service/standaloneServer.ts | 62 -------
plugins/pagerduty-backend/src/setupTests.ts | 18 --
12 files changed, 431 deletions(-)
delete mode 100644 packages/backend/src/plugins/pagerduty.ts
delete mode 100644 plugins/pagerduty-backend/.eslintrc.js
delete mode 100644 plugins/pagerduty-backend/README.md
delete mode 100644 plugins/pagerduty-backend/package.json
delete mode 100644 plugins/pagerduty-backend/src/index.ts
delete mode 100644 plugins/pagerduty-backend/src/run.ts
delete mode 100644 plugins/pagerduty-backend/src/service/router.test.ts
delete mode 100644 plugins/pagerduty-backend/src/service/router.ts
delete mode 100644 plugins/pagerduty-backend/src/service/standaloneServer.ts
delete mode 100644 plugins/pagerduty-backend/src/setupTests.ts
diff --git a/packages/backend/package.json b/packages/backend/package.json
index c31629e82e..ef55b007a7 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -31,7 +31,6 @@
"@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.25",
"@backstage/plugin-sentry-backend": "^0.1.1-alpha.25",
"@backstage/plugin-techdocs-backend": "^0.1.1-alpha.25",
- "@backstage/plugin-pagerduty-backend": "^0.1.1-alpha.25",
"@gitbeaker/node": "^23.5.0",
"@octokit/rest": "^18.0.0",
"azure-devops-node-api": "^10.1.1",
diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts
index 0ca17c7d7f..18fd44f089 100644
--- a/packages/backend/src/index.ts
+++ b/packages/backend/src/index.ts
@@ -46,7 +46,6 @@ import techdocs from './plugins/techdocs';
import graphql from './plugins/graphql';
import app from './plugins/app';
import { PluginEnvironment } from './types';
-import pagerduty from './plugins/pagerduty';
function makeCreateEnv(config: ConfigReader) {
const root = getRootLogger();
@@ -80,7 +79,6 @@ async function main() {
const kubernetesEnv = useHotMemoize(module, () => createEnv('kubernetes'));
const graphqlEnv = useHotMemoize(module, () => createEnv('graphql'));
const appEnv = useHotMemoize(module, () => createEnv('app'));
- const pagerdutyEnv = useHotMemoize(module, () => createEnv('pagerduty'));
const apiRouter = Router();
apiRouter.use('/catalog', await catalog(catalogEnv));
@@ -92,7 +90,6 @@ async function main() {
apiRouter.use('/kubernetes', await kubernetes(kubernetesEnv));
apiRouter.use('/proxy', await proxy(proxyEnv));
apiRouter.use('/graphql', await graphql(graphqlEnv));
- apiRouter.use('/pagerduty', await pagerduty(pagerdutyEnv));
apiRouter.use(notFoundHandler());
const service = createServiceBuilder(module)
diff --git a/packages/backend/src/plugins/pagerduty.ts b/packages/backend/src/plugins/pagerduty.ts
deleted file mode 100644
index 6dc3d8c9ba..0000000000
--- a/packages/backend/src/plugins/pagerduty.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2020 Spotify AB
- *
- * 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 { createRouter } from '@backstage/plugin-pagerduty-backend';
-import { PluginEnvironment } from '../types';
-
-export default async function createPlugin({
- logger,
- config,
-}: PluginEnvironment) {
- return await createRouter({
- logger,
- config,
- });
-}
diff --git a/plugins/pagerduty-backend/.eslintrc.js b/plugins/pagerduty-backend/.eslintrc.js
deleted file mode 100644
index 16a033dbc6..0000000000
--- a/plugins/pagerduty-backend/.eslintrc.js
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = {
- extends: [require.resolve('@backstage/cli/config/eslint.backend')],
-};
diff --git a/plugins/pagerduty-backend/README.md b/plugins/pagerduty-backend/README.md
deleted file mode 100644
index 099a94a85f..0000000000
--- a/plugins/pagerduty-backend/README.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# pagerduty
-
-Welcome to the pagerduty backend plugin!
-
-_This plugin was created through the Backstage CLI_
-
-## Getting started
-
-Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/pagerduty](http://localhost:3000/pagerduty).
-
-You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
-This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
-It is only meant for local development, and the setup for it can be found inside the [/dev](/dev) directory.
diff --git a/plugins/pagerduty-backend/package.json b/plugins/pagerduty-backend/package.json
deleted file mode 100644
index cad4ba268e..0000000000
--- a/plugins/pagerduty-backend/package.json
+++ /dev/null
@@ -1,40 +0,0 @@
-{
- "name": "@backstage/plugin-pagerduty-backend",
- "version": "0.1.1-alpha.25",
- "main": "src/index.ts",
- "types": "src/index.ts",
- "license": "Apache-2.0",
- "publishConfig": {
- "access": "public",
- "main": "dist/index.cjs.js",
- "types": "dist/index.d.ts"
- },
- "scripts": {
- "start": "backstage-cli backend:dev",
- "build": "backstage-cli backend:build",
- "lint": "backstage-cli lint",
- "test": "backstage-cli test",
- "prepack": "backstage-cli prepack",
- "postpack": "backstage-cli postpack",
- "clean": "backstage-cli clean"
- },
- "dependencies": {
- "@backstage/backend-common": "^0.1.1-alpha.25",
- "@backstage/config": "^0.1.1-alpha.25",
- "@types/express": "^4.17.6",
- "express": "^4.17.1",
- "express-promise-router": "^3.0.3",
- "winston": "^3.2.1",
- "node-fetch": "^2.6.1",
- "yn": "^4.0.0"
- },
- "devDependencies": {
- "@backstage/cli": "^0.1.1-alpha.25",
- "@types/supertest": "^2.0.8",
- "supertest": "^4.0.2",
- "msw": "^0.20.5"
- },
- "files": [
- "dist"
- ]
-}
diff --git a/plugins/pagerduty-backend/src/index.ts b/plugins/pagerduty-backend/src/index.ts
deleted file mode 100644
index 7612c392a2..0000000000
--- a/plugins/pagerduty-backend/src/index.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright 2020 Spotify AB
- *
- * 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 * from './service/router';
diff --git a/plugins/pagerduty-backend/src/run.ts b/plugins/pagerduty-backend/src/run.ts
deleted file mode 100644
index b96989e4b8..0000000000
--- a/plugins/pagerduty-backend/src/run.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2020 Spotify AB
- *
- * 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 { getRootLogger } from '@backstage/backend-common';
-import yn from 'yn';
-import { startStandaloneServer } from './service/standaloneServer';
-
-const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7000;
-const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
-const logger = getRootLogger();
-
-startStandaloneServer({ port, enableCors, logger }).catch(err => {
- logger.error(err);
- process.exit(1);
-});
-
-process.on('SIGINT', () => {
- logger.info('CTRL+C pressed; exiting.');
- process.exit(0);
-});
diff --git a/plugins/pagerduty-backend/src/service/router.test.ts b/plugins/pagerduty-backend/src/service/router.test.ts
deleted file mode 100644
index 0aaeafa379..0000000000
--- a/plugins/pagerduty-backend/src/service/router.test.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2020 Spotify AB
- *
- * 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 { getVoidLogger } from '@backstage/backend-common';
-import express from 'express';
-import request from 'supertest';
-
-import { createRouter } from './router';
-
-describe('createRouter', () => {
- let app: express.Express;
-
- beforeAll(async () => {
- const router = await createRouter({
- logger: getVoidLogger(),
- });
- app = express().use(router);
- });
-
- beforeEach(() => {
- jest.resetAllMocks();
- });
-
- describe('GET /health', () => {
- it('returns ok', async () => {
- const response = await request(app).get('/health');
-
- expect(response.status).toEqual(200);
- expect(response.body).toEqual({ status: 'ok' });
- });
- });
-});
diff --git a/plugins/pagerduty-backend/src/service/router.ts b/plugins/pagerduty-backend/src/service/router.ts
deleted file mode 100644
index 1fbab9b551..0000000000
--- a/plugins/pagerduty-backend/src/service/router.ts
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright 2020 Spotify AB
- *
- * 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 { errorHandler, useHotCleanup } from '@backstage/backend-common';
-import { Config } from '@backstage/config';
-import express from 'express';
-import Router from 'express-promise-router';
-import { Logger } from 'winston';
-import fetch from 'node-fetch';
-import { Response } from 'express';
-
-const router = Router();
-const API_URL = 'https://api.pagerduty.com';
-const EVENTS_API_URL = 'https://events.pagerduty.com/v2';
-
-type Options = {
- method: string;
- headers: {
- 'Content-Type': string;
- Accept: string;
- Authorization?: string;
- };
- body?: string;
-};
-
-async function request(
- url: string,
- options: any, // Options,
-): Promise {
- // TODO: handle errors better
- 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 await response.json();
- } catch (error) {
- throw new Error(error);
- }
-}
-
-async function services(token?: string) {
- // TODO: handle missing token differently
- if (!token) {
- return 'Missing Token';
- }
-
- const options = {
- method: 'GET',
- headers: {
- Authorization: `Token token=${token}`,
- Accept: 'application/vnd.pagerduty+json;version=2',
- 'Content-Type': 'application/json',
- },
- };
-
- let offset = 0;
- // TODO: Create type for this data
- const data: PagerDutyService[] = [];
-
- async function getData() {
- const result = await request(
- `${API_URL}/services?offset=${offset}`,
- options,
- );
-
- data.push(
- ...result.services.map(service => ({
- id: service.id,
- name: service.name,
- homepageUrl: service.html_url,
- })),
- );
-
- // TODO: remove offset when we are done
- if (offset < 2 && result.more) {
- ++offset;
- await getData();
- }
- }
-
- try {
- await getData();
- } catch (error) {
- throw new Error(error);
- }
-
- return data;
-}
-
-export interface RouterOptions {
- logger: Logger;
- config: Config;
-}
-
-type PagerDutyService = {
- // activeIncidents: any[];
- // escalationPolicy: any[];
- id: string;
- name: string;
- homepageUrl: string;
-};
-
-type PagerDutyResponse = {
- services: any[];
- more: boolean;
-};
-
-export async function createRouter(
- options: RouterOptions,
-): Promise {
- const { logger, config } = options;
- const token = config.getOptionalString('pagerduty.api_token') ?? undefined;
-
- let cachedData: any = [];
- let errorMessage: string;
-
- let cancel = false;
- const intervalId = setInterval(async () => {
- if (!cancel) {
- cancel = true;
- try {
- logger.info('Fetching data from PagerDuty API');
- cachedData = await services(token);
- } catch (error) {
- logger.error(`Failed to fetch data, ${error.message}`);
- errorMessage = error.message;
- } finally {
- cancel = false;
- }
- }
- }, 10000);
-
- useHotCleanup(module, () => clearInterval(intervalId));
-
- router.use(express.json());
-
- router.get(
- '/services',
- async (_, response: Response) => {
- if (errorMessage) {
- response.status(500).send({ error: errorMessage });
- } else {
- response.send({ services: cachedData });
- }
- },
- );
-
- router.use(errorHandler());
-
- return router;
-}
diff --git a/plugins/pagerduty-backend/src/service/standaloneServer.ts b/plugins/pagerduty-backend/src/service/standaloneServer.ts
deleted file mode 100644
index 73f928bef4..0000000000
--- a/plugins/pagerduty-backend/src/service/standaloneServer.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2020 Spotify AB
- *
- * 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.
- */
-/*
- * Copyright 2020 Spotify AB
- *
- * 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 { createServiceBuilder } from '@backstage/backend-common';
-import { Server } from 'http';
-import { Logger } from 'winston';
-import { createRouter } from './router';
-
-export interface ServerOptions {
- port: number;
- enableCors: boolean;
- logger: Logger;
-}
-
-export async function startStandaloneServer(
- options: ServerOptions,
-): Promise {
- const logger = options.logger.child({ service: 'pagerduty-backend' });
- logger.debug('Starting application server...');
- const router = await createRouter({
- logger,
- });
-
- const service = createServiceBuilder(module)
- .enableCors({ origin: 'http://localhost:3000' })
- .addRouter('/pagerduty', router);
-
- return await service.start().catch(err => {
- logger.error(err);
- process.exit(1);
- });
-}
-
-module.hot?.accept();
diff --git a/plugins/pagerduty-backend/src/setupTests.ts b/plugins/pagerduty-backend/src/setupTests.ts
deleted file mode 100644
index a5907fd52f..0000000000
--- a/plugins/pagerduty-backend/src/setupTests.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * Copyright 2020 Spotify AB
- *
- * 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 {};
-global.fetch = require('node-fetch');
From e4bec4e17e484a03561e3ff81c161496bcc988b3 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Tue, 17 Nov 2020 10:01:59 +0100
Subject: [PATCH 23/52] add proxy to config
---
app-config.yaml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/app-config.yaml b/app-config.yaml
index 715d564feb..fbeba50835 100644
--- a/app-config.yaml
+++ b/app-config.yaml
@@ -41,6 +41,12 @@ proxy:
X-Api-Key:
$env: NEW_RELIC_REST_API_KEY
+ '/pagerduty':
+ target: https://api.pagerduty.com
+ headers:
+ Authorization:
+ $env: PAGERDUTY_TOKEN
+
organization:
name: My Company
From 2e8c842ad73c5fe79def3daabc85287a41a26038 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Tue, 17 Nov 2020 11:11:35 +0100
Subject: [PATCH 24/52] update versions
---
.../catalog/src/components/AboutCard/index.ts | 1 +
plugins/catalog/src/index.ts | 3 +--
plugins/pagerduty/package.json | 16 ++++++++--------
3 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/plugins/catalog/src/components/AboutCard/index.ts b/plugins/catalog/src/components/AboutCard/index.ts
index 93765ff942..0491d467f8 100644
--- a/plugins/catalog/src/components/AboutCard/index.ts
+++ b/plugins/catalog/src/components/AboutCard/index.ts
@@ -15,3 +15,4 @@
*/
export { AboutCard } from './AboutCard';
+export { IconLinkVertical } from './IconLinkVertical';
diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts
index f0e8b103e6..5949209128 100644
--- a/plugins/catalog/src/index.ts
+++ b/plugins/catalog/src/index.ts
@@ -15,9 +15,8 @@
*/
export * from '@backstage/catalog-client';
-export { AboutCard } from './components/AboutCard';
+export { AboutCard, IconLinkVertical } from './components/AboutCard';
export { EntityPageLayout } from './components/EntityPageLayout';
-export { IconLinkVertical } from './components/AboutCard/IconLinkVertical';
export { Router } from './components/Router';
export { useEntityCompoundName } from './components/useEntityCompoundName';
export { EntityContext, useEntity } from './hooks/useEntity';
diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json
index 29518bd858..2fce2ebef3 100644
--- a/plugins/pagerduty/package.json
+++ b/plugins/pagerduty/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-pagerduty",
- "version": "0.1.1-alpha.25",
+ "version": "0.2.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,11 +20,11 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/core": "^0.1.1-alpha.25",
- "@backstage/theme": "^0.1.1-alpha.25",
- "@backstage/catalog-model": "^0.1.1-alpha.25",
- "@backstage/test-utils": "^0.1.1-alpha.25",
- "@backstage/plugin-catalog": "^0.1.1-alpha.25",
+ "@backstage/core": "^0.3.0",
+ "@backstage/theme": "^0.2.1",
+ "@backstage/catalog-model": "^0.2.0",
+ "@backstage/test-utils": "^0.1.2",
+ "@backstage/plugin-catalog": "^0.2.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -34,8 +34,8 @@
"moment": "^2.27.0"
},
"devDependencies": {
- "@backstage/cli": "^0.1.1-alpha.25",
- "@backstage/dev-utils": "^0.1.1-alpha.25",
+ "@backstage/cli": "^0.2.0",
+ "@backstage/dev-utils": "^0.1.3",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
From 22b0500d76118aa07aca589a57d627f189facf7e Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Wed, 18 Nov 2020 09:51:33 +0100
Subject: [PATCH 25/52] small fixes
---
.../app/src/components/catalog/EntityPage.tsx | 3 +-
plugins/pagerduty/src/api/client.ts | 10 +--
plugins/pagerduty/src/api/types.ts | 10 +++
plugins/pagerduty/src/assets/pd.svg | 13 ----
.../components/Escalation/EscalationUser.tsx | 4 +-
.../components/Incident/IncidentListItem.tsx | 2 +-
.../TriggerButton/TriggerButton.tsx | 2 +-
.../TriggerDialog/TriggerDialog.tsx | 65 ++++++++++++-------
8 files changed, 60 insertions(+), 49 deletions(-)
delete mode 100644 plugins/pagerduty/src/assets/pd.svg
diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx
index 17231abb9a..6099b6ffe0 100644
--- a/packages/app/src/components/catalog/EntityPage.tsx
+++ b/packages/app/src/components/catalog/EntityPage.tsx
@@ -70,7 +70,8 @@ import {
isPluginApplicableToEntity as isPagerDutyAvailable,
PagerDutyCard,
} from '@backstage/plugin-pagerduty';
-import { Router as BuildKiteRouter,
+import {
+ Router as BuildKiteRouter,
isPluginApplicableToEntity as isBuildKiteAvailable,
} from '@roadiehq/backstage-plugin-buildkite';
diff --git a/plugins/pagerduty/src/api/client.ts b/plugins/pagerduty/src/api/client.ts
index 985bb25e5a..c9d40a1694 100644
--- a/plugins/pagerduty/src/api/client.ts
+++ b/plugins/pagerduty/src/api/client.ts
@@ -27,13 +27,9 @@ import {
} from '../components/types';
import { PagerDutyClient } from './types';
-export class UnauthorizedError extends Error {
- constructor() {
- super();
- }
-}
+export class UnauthorizedError extends Error {}
-export const pagerDutyApiRef = createApiRef({
+export const pagerDutyApiRef = createApiRef({
id: 'plugin.pagerduty.api',
description: 'Used to fetch data from PagerDuty API',
});
@@ -65,7 +61,7 @@ export class PagerDutyClientApi implements PagerDutyClient {
return oncalls;
}
- triggerPagerDutyAlarm(
+ triggerAlarm(
integrationKey: string,
source: string,
description: string,
diff --git a/plugins/pagerduty/src/api/types.ts b/plugins/pagerduty/src/api/types.ts
index cb9d7fc5bc..c07834a213 100644
--- a/plugins/pagerduty/src/api/types.ts
+++ b/plugins/pagerduty/src/api/types.ts
@@ -34,4 +34,14 @@ export interface PagerDutyClient {
*
*/
getOnCallByPolicyId(policyId: string): Promise;
+
+ /**
+ * Triggers an incident to whoever is on-call.
+ */
+ triggerAlarm(
+ integrationKey: string,
+ source: string,
+ description: string,
+ userName: string,
+ ): Promise;
}
diff --git a/plugins/pagerduty/src/assets/pd.svg b/plugins/pagerduty/src/assets/pd.svg
deleted file mode 100644
index 237ffa2011..0000000000
--- a/plugins/pagerduty/src/assets/pd.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
- pd_icon
- Created with Sketch.
-
-
-
-
-
-
-
-
diff --git a/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx b/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx
index e20a5f7567..adc8d4e6d7 100644
--- a/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx
+++ b/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx
@@ -52,12 +52,12 @@ export const EscalationUser = ({ user }: { user: User }) => {
secondary={user.email}
/>
-
+
-
+
{
}
/>
-
+
{
setDescription(event.target.value);
};
- const [{ value, loading, error }, triggerAlarm] = useAsyncFn(
+ const [{ value, loading, error }, handleTriggerAlarm] = useAsyncFn(
async (desc: string) => {
- return await api.triggerPagerDutyAlarm(
+ return await api.triggerAlarm(
integrationKey,
window.location.toString(),
desc,
@@ -82,34 +91,41 @@ export const TriggerDialog = ({ name, integrationKey, onClose }: Props) => {
}, [value, error, alertApi, onClose, userId]);
return (
-
+
- This action will send PagerDuty alarms and notifications to on-call
- people responsible for {name}.
+ This action will trigger an incident for "{name}" .
-
- Note: If the issue you are seeing does not need urgent attention,
- please get in touch with the responsible team using their preferred
- communications channel. For most views, you can find links to support
- and information channels by clicking the support button in the top
- right corner of the page. If the issue is urgent, please don't
- hesitate to trigger the alert.
-
-
+
+ {/* "Note" */}
+
+ If the issue you are seeing does not need urgent attention, please
+ get in touch with the responsible team using their preferred
+ communications channel. You can find information about the owner of
+ this entity in the "About" card. If the issue is urgent, please
+ don't hesitate to trigger the alert.
+
+
+
Please describe the problem you want to report. Be as descriptive as
- possible. Your Spotify username and a reference to the current page
- will automatically be sent amended to the alarm so that we can debug
- the issue and reach out to you if necessary.
-
+ possible. Your signed in user and a reference to the current page will
+ automatically be amended to the alarm so that the receiver can reach
+ out to you if necessary.
+
@@ -119,15 +135,16 @@ export const TriggerDialog = ({ name, integrationKey, onClose }: Props) => {
id="trigger"
color="secondary"
disabled={!description || loading}
- onClick={() => triggerAlarm(description)}
+ variant="contained"
+ onClick={() => handleTriggerAlarm(description)}
+ endIcon={loading && }
>
- Trigger
+ Trigger Incident
-
+
Close
- {loading && }
);
};
From bc161fba4ebeedac9b39774ee4aacf8fab488de4 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Fri, 20 Nov 2020 11:33:53 +0100
Subject: [PATCH 26/52] add readme
---
plugins/pagerduty/README.md | 92 +++++++++++++++++++++++++++++++++----
1 file changed, 84 insertions(+), 8 deletions(-)
diff --git a/plugins/pagerduty/README.md b/plugins/pagerduty/README.md
index 0335cb5a9b..9c5c0b2485 100644
--- a/plugins/pagerduty/README.md
+++ b/plugins/pagerduty/README.md
@@ -1,13 +1,89 @@
-# pagerduty
+# PagerDuty
-Welcome to the pagerduty plugin!
+## Overview
-_This plugin was created through the Backstage CLI_
+This plugin displays PagerDuty information about an entity such as if there are any active incidents and how the escalation policy looks like.
-## Getting started
+There is also an easy way to trigger an alarm directly to the person who is currently on-call.
-Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/pagerduty](http://localhost:3000/pagerduty).
+This plugin requires that entities are annotated with an [integration key](https://support.pagerduty.com/docs/services-and-integrations#add-integrations-to-an-existing-service). See more further down in this document.
-You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
-This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
-It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory.
+This plugin provides:
+
+- A list of incidents
+- A way to trigger an alarm to the person on-call
+- Information details about the person on-call
+
+## Setup instructions
+
+Install the plugin:
+
+```bash
+yarn add @backstage/plugin-pagerduty
+```
+
+Add it to the app in `plugins.ts`:
+
+```ts
+export { plugin as Pagerduty } from '@backstage/plugin-pagerduty';
+```
+
+Add it to the `EntityPage.ts`:
+
+```ts
+import {
+ isPluginApplicableToEntity as isPagerDutyAvailable,
+ PagerDutyCard,
+} from '@backstage/plugin-pagerduty';
+// add to code
+{
+ isPagerDutyAvailable(entity) && (
+
+
+
+ );
+}
+```
+
+## Client configuration
+
+The PagerDutyClient can be configured with the appropriate urls:
+
+In `apis.ts`:
+
+```ts
+createApiFactory({
+ api: pagerDutyApiRef,
+ deps: { configApi: configApiRef },
+ factory: ({ configApi }) =>
+ new PagerDutyClientApi({
+ api_url: `https://api.pagerduty.com`,
+ events_url: 'https://events.pagerduty.com/v2',
+ }),
+}),
+```
+
+## Providing the API Token
+
+In order for the client to make requests to the [PagerDuty API](https://developer.pagerduty.com/docs/rest-api-v2/rest-api/) it needs an [API Token](https://support.pagerduty.com/docs/generating-api-keys#generating-a-general-access-rest-api-key).
+
+Then start the backend passing the token as an environment variable:
+
+```bash
+$ PAGERDUTY_TOKEN='Token token=' yarn start
+```
+
+This will proxy the request by adding `Authorization` header with the provided token.
+
+## Integration Key
+
+The information displayed for each entity is based on the [integration key](https://support.pagerduty.com/docs/services-and-integrations#add-integrations-to-an-existing-service).
+
+### Adding the integration key to the entity annotation
+
+If you want to use this plugin for an entity, you need to label it with the below annotation:
+
+```yml
+annotations:
+ pagerduty.com/integration-key: [INTEGRATION_KEY]
+```
From d14f125ed1769b2c8fee7c51f572eb304b4af43d Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Fri, 20 Nov 2020 12:03:27 +0100
Subject: [PATCH 27/52] update pagerduty after triggering new alarm
---
.../Escalation/EscalationPolicy.tsx | 52 ++++++++---
.../components/Escalation/EscalationUser.tsx | 7 +-
.../components/Incident/IncidentListItem.tsx | 43 +++++----
.../src/components/Incident/Incidents.tsx | 67 ++++++++++----
.../src/components/PagerdutyCard.tsx | 88 ++++++++++++++-----
.../TriggerButton/TriggerButton.tsx | 71 ---------------
.../src/components/TriggerButton/index.ts | 17 ----
.../TriggerDialog/TriggerDialog.tsx | 65 +++++++-------
plugins/pagerduty/src/components/types.tsx | 10 ++-
plugins/pagerduty/src/index.ts | 7 +-
10 files changed, 229 insertions(+), 198 deletions(-)
delete mode 100644 plugins/pagerduty/src/components/TriggerButton/TriggerButton.tsx
delete mode 100644 plugins/pagerduty/src/components/TriggerButton/index.ts
diff --git a/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx b/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx
index cbce0ce4a3..ccf70abb0b 100644
--- a/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx
+++ b/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx
@@ -15,21 +15,47 @@
*/
import React from 'react';
-import { List, ListSubheader } from '@material-ui/core';
-import { User } from '../types';
+import { List, ListSubheader, LinearProgress } from '@material-ui/core';
import { EscalationUsersEmptyState } from './EscalationUsersEmptyState';
import { EscalationUser } from './EscalationUser';
+import { useAsync } from 'react-use';
+import { pagerDutyApiRef } from '../../api';
+import { useApi } from '@backstage/core';
+import { Alert } from '@material-ui/lab';
-type EscalationPolicyProps = {
- users: User[];
+type Props = {
+ policyId: string;
};
-export const EscalationPolicy = ({ users }: EscalationPolicyProps) => (
- ON CALL}>
- {users.length ? (
- users.map((user, index) => )
- ) : (
-
- )}
-
-);
+export const EscalationPolicy = ({ policyId }: Props) => {
+ const api = useApi(pagerDutyApiRef);
+
+ const { value: users, loading, error } = useAsync(async () => {
+ const oncalls = await api.getOnCallByPolicyId(policyId);
+ const users = oncalls.map(oncall => oncall.user);
+
+ return users;
+ });
+
+ if (error) {
+ return (
+
+ Error encountered while fetching information. {error.message}
+
+ );
+ }
+
+ if (loading) {
+ return ;
+ }
+
+ return users!.length ? (
+ ON CALL}>
+ {users!.map((user, index) => (
+
+ ))}
+
+ ) : (
+
+ );
+};
diff --git a/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx b/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx
index adc8d4e6d7..642c9ecd96 100644
--- a/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx
+++ b/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx
@@ -36,8 +36,13 @@ const useStyles = makeStyles({
},
});
-export const EscalationUser = ({ user }: { user: User }) => {
+type Props = {
+ user: User;
+};
+
+export const EscalationUser = ({ user }: Props) => {
const classes = useStyles();
+
return (
diff --git a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx
index 6e22ccbbac..7a97dad281 100644
--- a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx
+++ b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx
@@ -31,10 +31,6 @@ import moment from 'moment';
import { Incident } from '../types';
import PagerdutyIcon from '../PagerDutyIcon';
-type IncidentListItemProps = {
- incident: Incident;
-};
-
const useStyles = makeStyles({
denseListIcon: {
marginRight: 0,
@@ -51,9 +47,15 @@ const useStyles = makeStyles({
},
});
-export const IncidentListItem = ({ incident }: IncidentListItemProps) => {
+type Props = {
+ incident: Incident;
+};
+
+export const IncidentListItem = ({ incident }: Props) => {
const classes = useStyles();
- const user = incident.assignments[0].assignee;
+ const user = incident.assignments[0]?.assignee;
+ const createdAt = moment(incident.created_at).fromNow();
+
return (
@@ -68,25 +70,28 @@ export const IncidentListItem = ({ incident }: IncidentListItemProps) => {
- {incident.title}
-
- }
+ primary={incident.title}
+ primaryTypographyProps={{
+ variant: 'body1',
+ className: classes.listItemPrimary,
+ }}
secondary={
-
- Created {moment(incident.created_at).fromNow()}, assigned to{' '}
- {(incident?.assignments[0]?.assignee?.summary && (
- {user.summary}
- )) ||
- 'nobody'}
-
+
+ Created {createdAt} and assigned to{' '}
+
+ {user?.summary ?? 'nobody'}
+
+
}
/>
void;
};
-export const Incidents = ({ incidents }: IncidentsProps) => (
- {incidents.length && 'INCIDENTS'}}
- >
- {incidents.length ? (
- incidents.map((incident, index) => (
+export const Incidents = ({
+ serviceId,
+ shouldRefreshIncidents,
+ setShouldRefreshIncidents,
+}: Props) => {
+ const api = useApi(pagerDutyApiRef);
+
+ const [{ value: incidents, loading, error }, getIncidents] = useAsyncFn(
+ async () => {
+ setShouldRefreshIncidents(false);
+ return await api.getIncidentsByServiceId(serviceId);
+ },
+ );
+
+ useEffect(() => {
+ getIncidents();
+ }, [shouldRefreshIncidents, getIncidents]);
+
+ if (error) {
+ return (
+
+ Error encountered while fetching information. {error.message}
+
+ );
+ }
+
+ if (loading) {
+ return ;
+ }
+
+ return incidents?.length ? (
+ INCIDENTS}>
+ {incidents!.map((incident, index) => (
- ))
- ) : (
-
- )}
-
-);
+ ))}
+
+ ) : (
+
+ );
+};
diff --git a/plugins/pagerduty/src/components/PagerdutyCard.tsx b/plugins/pagerduty/src/components/PagerdutyCard.tsx
index 6b6d466850..f24e91a9e0 100644
--- a/plugins/pagerduty/src/components/PagerdutyCard.tsx
+++ b/plugins/pagerduty/src/components/PagerdutyCard.tsx
@@ -13,10 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React from 'react';
+import React, { useState } from 'react';
import { useApi, EmptyState } from '@backstage/core';
import { Entity } from '@backstage/catalog-model';
import {
+ Button,
Card,
CardContent,
CardHeader,
@@ -26,13 +27,13 @@ import {
} from '@material-ui/core';
import { Incidents } from './Incident';
import { EscalationPolicy } from './Escalation';
-import { TriggerButton } from './TriggerButton';
import { useAsync } from 'react-use';
import { Alert } from '@material-ui/lab';
import { pagerDutyApiRef, UnauthorizedError } from '../api';
import { IconLinkVertical } from '@backstage/plugin-catalog';
import PagerDutyIcon from './PagerDutyIcon';
-import ReportProblemIcon from '@material-ui/icons/ReportProblem';
+import AlarmAddIcon from '@material-ui/icons/AlarmAdd';
+import { TriggerDialog } from './TriggerDialog';
const useStyles = makeStyles(theme => ({
links: {
@@ -42,6 +43,19 @@ const useStyles = makeStyles(theme => ({
gridAutoColumns: 'min-content',
gridGap: theme.spacing(3),
},
+ triggerAlarm: {
+ paddingTop: 0,
+ paddingBottom: 0,
+ fontSize: '0.7rem',
+ textTransform: 'uppercase',
+ fontWeight: 600,
+ letterSpacing: 1.2,
+ lineHeight: 1.5,
+ '&:hover, &:focus, &.focus': {
+ backgroundColor: 'transparent',
+ textDecoration: 'none',
+ },
+ },
}));
export const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key';
@@ -56,35 +70,45 @@ type Props = {
export const PagerDutyCard = ({ entity }: Props) => {
const classes = useStyles();
const api = useApi(pagerDutyApiRef);
+ const [showDialog, setShowDialog] = useState(false);
+ const [shouldRefreshIncidents, setShouldRefreshIncidents] = useState(
+ false,
+ );
+ const integrationKey = entity.metadata.annotations![
+ PAGERDUTY_INTEGRATION_KEY
+ ];
- const { value, loading, error } = useAsync(async () => {
- const integrationKey = entity.metadata.annotations![
- PAGERDUTY_INTEGRATION_KEY
- ];
-
+ const { value: service, loading, error } = useAsync(async () => {
const services = await api.getServiceByIntegrationKey(integrationKey);
- const incidents = await api.getIncidentsByServiceId(services[0].id);
- const oncalls = await api.getOnCallByPolicyId(
- services[0].escalation_policy.id,
- );
- const users = oncalls.map(oncall => oncall.user);
return {
- incidents,
- users,
id: services[0].id,
name: services[0].name,
- homepageUrl: services[0].html_url,
+ url: services[0].html_url,
+ policyId: services[0].escalation_policy.id,
};
});
+ const handleDialog = () => {
+ setShowDialog(!showDialog);
+ };
+
if (error) {
if (error instanceof UnauthorizedError) {
return (
+ Read More
+
+ }
/>
);
}
@@ -102,12 +126,21 @@ export const PagerDutyCard = ({ entity }: Props) => {
const pagerdutyLink = {
title: 'View in PagerDuty',
- href: value!.homepageUrl,
+ href: service!.url,
};
const triggerAlarm = {
title: 'Trigger Alarm',
- action: ,
+ action: (
+
+ Trigger Alarm
+
+ ),
};
return (
@@ -123,7 +156,7 @@ export const PagerDutyCard = ({ entity }: Props) => {
/>
}
+ icon={ }
action={triggerAlarm.action}
/>
@@ -131,8 +164,19 @@ export const PagerDutyCard = ({ entity }: Props) => {
/>
-
-
+
+
+
);
diff --git a/plugins/pagerduty/src/components/TriggerButton/TriggerButton.tsx b/plugins/pagerduty/src/components/TriggerButton/TriggerButton.tsx
deleted file mode 100644
index a4f012ac47..0000000000
--- a/plugins/pagerduty/src/components/TriggerButton/TriggerButton.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright 2020 Spotify AB
- *
- * 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, { useState } from 'react';
-import { Button, makeStyles } from '@material-ui/core';
-import { TriggerDialog } from '../TriggerDialog';
-import { Entity } from '@backstage/catalog-model';
-import { PAGERDUTY_INTEGRATION_KEY } from '../PagerDutyCard';
-
-const useStyles = makeStyles({
- triggerAlarm: {
- paddingTop: 0,
- paddingBottom: 0,
- fontSize: '0.7rem',
- textTransform: 'uppercase',
- fontWeight: 600,
- letterSpacing: 1.2,
- lineHeight: 1.5,
- '&:hover, &:focus, &.focus': {
- backgroundColor: 'transparent',
- textDecoration: 'none',
- },
- },
-});
-
-type Props = {
- entity: Entity;
-};
-
-export const TriggerButton = ({ entity }: Props) => {
- const [showDialog, setShowDialog] = useState(false);
- const classes = useStyles();
-
- const handleDialog = () => {
- setShowDialog(!showDialog);
- };
-
- return (
- <>
-
- Trigger Alarm
-
- {showDialog && (
-
- )}
- >
- );
-};
diff --git a/plugins/pagerduty/src/components/TriggerButton/index.ts b/plugins/pagerduty/src/components/TriggerButton/index.ts
deleted file mode 100644
index 6a58a4f43f..0000000000
--- a/plugins/pagerduty/src/components/TriggerButton/index.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright 2020 Spotify AB
- *
- * 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 { TriggerButton } from './TriggerButton';
diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx
index 4156074460..e6bb6d107d 100644
--- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx
+++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx
@@ -19,67 +19,61 @@ import {
Dialog,
DialogTitle,
TextField,
- makeStyles,
DialogActions,
Button,
DialogContent,
Typography,
CircularProgress,
} from '@material-ui/core';
-import {
- Progress,
- useApi,
- alertApiRef,
- identityApiRef,
- WarningPanel,
-} from '@backstage/core';
+import { useApi, alertApiRef, identityApiRef } from '@backstage/core';
import { useAsyncFn } from 'react-use';
import { pagerDutyApiRef } from '../../api';
-import { Alert, AlertTitle } from '@material-ui/lab';
-
-const useStyles = makeStyles({
- warningText: {
- border: '1px solid rgba(245, 155, 35, 0.5)',
- backgroundColor: 'rgba(245, 155, 35, 0.2)',
- padding: '0.5em 1em',
- },
-});
+import { Alert } from '@material-ui/lab';
type Props = {
name: string;
integrationKey: string;
- onClose: () => void;
+ showDialog: boolean;
+ handleDialog: () => void;
+ setShouldRefreshIncidents: (shouldRefreshIncidents: boolean) => void;
};
-export const TriggerDialog = ({ name, integrationKey, onClose }: Props) => {
- const classes = useStyles();
- const [description, setDescription] = useState('');
+export const TriggerDialog = ({
+ name,
+ integrationKey,
+ showDialog,
+ handleDialog,
+ setShouldRefreshIncidents,
+}: 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);
- };
+ const [description, setDescription] = useState('');
const [{ value, loading, error }, handleTriggerAlarm] = useAsyncFn(
- async (desc: string) => {
- return await api.triggerAlarm(
+ async (desc: string) =>
+ await api.triggerAlarm(
integrationKey,
window.location.toString(),
desc,
userId,
- );
- },
+ ),
);
+ const descriptionChanged = (
+ event: React.ChangeEvent,
+ ) => {
+ setDescription(event.target.value);
+ };
+
useEffect(() => {
if (value) {
alertApi.post({
message: `Alarm successfully triggered by ${userId}`,
});
- onClose();
+ setShouldRefreshIncidents(true);
+ handleDialog();
}
if (error) {
@@ -88,16 +82,19 @@ export const TriggerDialog = ({ name, integrationKey, onClose }: Props) => {
severity: 'error',
});
}
- }, [value, error, alertApi, onClose, userId]);
+ }, [value, error, alertApi, handleDialog, userId, setShouldRefreshIncidents]);
+
+ if (!showDialog) {
+ return null;
+ }
return (
-
+
This action will trigger an incident for "{name}" .
- {/* "Note" */}
If the issue you are seeing does not need urgent attention, please
get in touch with the responsible team using their preferred
@@ -141,7 +138,7 @@ export const TriggerDialog = ({ name, integrationKey, onClose }: Props) => {
>
Trigger Incident
-
+
Close
diff --git a/plugins/pagerduty/src/components/types.tsx b/plugins/pagerduty/src/components/types.tsx
index 9c9e29cb01..e968656e65 100644
--- a/plugins/pagerduty/src/components/types.tsx
+++ b/plugins/pagerduty/src/components/types.tsx
@@ -18,10 +18,10 @@ export type Incident = {
id: string;
title: string;
status: string;
- homepageUrl: string;
+ html_url: string;
assignments: [
{
- assignee: User;
+ assignee: Assignee;
},
];
serviceId: string;
@@ -43,6 +43,12 @@ export type OnCall = {
user: User;
};
+export type Assignee = {
+ id: string;
+ summary: string;
+ html_url: string;
+};
+
export type User = {
id: string;
summary: string;
diff --git a/plugins/pagerduty/src/index.ts b/plugins/pagerduty/src/index.ts
index 58dc39932d..1e8e549ac9 100644
--- a/plugins/pagerduty/src/index.ts
+++ b/plugins/pagerduty/src/index.ts
@@ -17,4 +17,9 @@ export { plugin } from './plugin';
export {
isPluginApplicableToEntity,
PagerDutyCard,
-} from './components/PagerDutyCard';
+} from './components/PagerdutyCard';
+export {
+ PagerDutyClientApi,
+ pagerDutyApiRef,
+ UnauthorizedError,
+} from './api/client';
From 5519358df8ec1d4d2e39b24d991ec9f110ef92d9 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Fri, 20 Nov 2020 12:05:51 +0100
Subject: [PATCH 28/52] update tests
---
.../components/Escalation/Escalation.test.tsx | 80 +++++++--
.../components/Incident/Incidents.test.tsx | 151 +++++++++++------
.../src/components/PagerDutyCard.test.tsx | 153 ++++++++++++++++++
.../TriggerDialog/TriggerDialog.test.tsx | 27 ++--
4 files changed, 335 insertions(+), 76 deletions(-)
create mode 100644 plugins/pagerduty/src/components/PagerDutyCard.test.tsx
diff --git a/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx b/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx
index 4fa65eb999..15ff3278a0 100644
--- a/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx
+++ b/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx
@@ -14,34 +14,82 @@
* limitations under the License.
*/
import React from 'react';
-import { render } from '@testing-library/react';
+import { render, waitFor } from '@testing-library/react';
import { EscalationPolicy } from './EscalationPolicy';
import { wrapInTestApp } from '@backstage/test-utils';
import { User } from '../types';
+import { ApiProvider, ApiRegistry } from '@backstage/core';
+import { pagerDutyApiRef } from '../../api';
-const escalations: User[] = [
- {
- name: 'person1',
- id: 'p1',
- summary: 'person1',
- email: 'person1@example.com',
- html_url: 'http://a.com/id1',
- },
-];
+const mockPagerDutyApi = {
+ getOnCallByPolicyId: () => [],
+};
+const apis = ApiRegistry.from([[pagerDutyApiRef, mockPagerDutyApi]]);
describe('Escalation', () => {
- it('render emptyState', () => {
- const { getByText } = render(
- wrapInTestApp( ),
+ it('Handles an empty response', async () => {
+ mockPagerDutyApi.getOnCallByPolicyId = jest
+ .fn()
+ .mockImplementationOnce(async () => []);
+
+ const { getByText, queryByTestId } = render(
+ wrapInTestApp(
+
+
+ ,
+ ),
);
+ await waitFor(() => !queryByTestId('progress'));
+
expect(getByText('Empty escalation policy')).toBeInTheDocument();
+ expect(mockPagerDutyApi.getOnCallByPolicyId).toHaveBeenCalledWith('456');
});
- it('render escalation list', () => {
- const { getByText } = render(
- wrapInTestApp( ),
+ it('Render a list of users', async () => {
+ mockPagerDutyApi.getOnCallByPolicyId = jest
+ .fn()
+ .mockImplementationOnce(async () => [
+ {
+ user: {
+ name: 'person1',
+ id: 'p1',
+ summary: 'person1',
+ email: 'person1@example.com',
+ html_url: 'http://a.com/id1',
+ } as User,
+ },
+ ]);
+
+ const { getByText, queryByTestId } = render(
+ wrapInTestApp(
+
+
+ ,
+ ),
);
+ await waitFor(() => !queryByTestId('progress'));
+
expect(getByText('person1')).toBeInTheDocument();
expect(getByText('person1@example.com')).toBeInTheDocument();
+ expect(mockPagerDutyApi.getOnCallByPolicyId).toHaveBeenCalledWith('abc');
+ });
+
+ it('Handles errors', async () => {
+ mockPagerDutyApi.getOnCallByPolicyId = jest
+ .fn()
+ .mockRejectedValueOnce(new Error('Error message'));
+
+ const { getByText, queryByTestId } = render(
+ wrapInTestApp(
+
+
+ ,
+ ),
+ );
+ await waitFor(() => !queryByTestId('progress'));
+
+ expect(
+ getByText('Error encountered while fetching information. Error message'),
+ ).toBeInTheDocument();
});
});
diff --git a/plugins/pagerduty/src/components/Incident/Incidents.test.tsx b/plugins/pagerduty/src/components/Incident/Incidents.test.tsx
index fbe3783d30..6baa410021 100644
--- a/plugins/pagerduty/src/components/Incident/Incidents.test.tsx
+++ b/plugins/pagerduty/src/components/Incident/Incidents.test.tsx
@@ -14,65 +14,100 @@
* limitations under the License.
*/
import React from 'react';
-import { render } from '@testing-library/react';
+import { render, waitFor } from '@testing-library/react';
import { Incidents } from './Incidents';
import { wrapInTestApp } from '@backstage/test-utils';
+import { ApiProvider, ApiRegistry } from '@backstage/core';
+import { pagerDutyApiRef } from '../../api';
import { Incident } from '../types';
-const incidents: Incident[] = [
- {
- id: 'id1',
- status: 'triggered',
- title: 'title1',
- created_at: '2020-11-06T00:00:00Z',
- assignments: [
- {
- assignee: {
- name: 'person1',
- id: 'p1',
- summary: 'person1',
- email: 'person1@example.com',
- html_url: 'http://a.com/id1',
- },
- },
- ],
- homepageUrl: 'http://a.com/id1',
- serviceId: 'sId1',
- },
- {
- id: 'id2',
- status: 'acknowledged',
- title: 'title2',
- created_at: '2020-11-07T00:00:00Z',
- assignments: [
- {
- assignee: {
- name: 'person2',
- id: 'p2',
- summary: 'person2',
- email: 'person2@example.com',
- html_url: 'http://a.com/id2',
- },
- },
- ],
- homepageUrl: 'http://a.com/id2',
- serviceId: 'sId2',
- },
-];
+const mockPagerDutyApi = {
+ getIncidentsByServiceId: () => [],
+};
+const apis = ApiRegistry.from([[pagerDutyApiRef, mockPagerDutyApi]]);
describe('Incidents', () => {
- it('renders an empty state is there are no incidents', () => {
- const { getByText } = render(wrapInTestApp( ));
+ it('Renders an empty state when there are no incidents', async () => {
+ mockPagerDutyApi.getIncidentsByServiceId = jest
+ .fn()
+ .mockImplementationOnce(async () => []);
+
+ const { getByText, queryByTestId } = render(
+ wrapInTestApp(
+
+ {}}
+ />
+ ,
+ ),
+ );
+ await waitFor(() => !queryByTestId('progress'));
expect(
getByText('Nice! No incidents are assigned to you!'),
).toBeInTheDocument();
});
- it('renders all incidents', () => {
- const { getByText, getByTitle, getAllByTitle, getByLabelText } = render(
- wrapInTestApp( ),
- );
+ it('Renders all incidents', async () => {
+ mockPagerDutyApi.getIncidentsByServiceId = jest.fn().mockImplementationOnce(
+ async () =>
+ [
+ {
+ id: 'id1',
+ status: 'triggered',
+ title: 'title1',
+ created_at: '2020-11-06T00:00:00Z',
+ assignments: [
+ {
+ assignee: {
+ id: 'p1',
+ summary: 'person1',
+ html_url: 'http://a.com/id1',
+ },
+ },
+ ],
+ html_url: 'http://a.com/id1',
+ serviceId: 'sId1',
+ },
+ {
+ id: 'id2',
+ status: 'acknowledged',
+ title: 'title2',
+ created_at: '2020-11-07T00:00:00Z',
+ assignments: [
+ {
+ assignee: {
+ id: 'p2',
+ summary: 'person2',
+ html_url: 'http://a.com/id2',
+ },
+ },
+ ],
+ html_url: 'http://a.com/id2',
+ serviceId: 'sId2',
+ },
+ ] as Incident[],
+ );
+ const {
+ getByText,
+ getByTitle,
+ getAllByTitle,
+ getByLabelText,
+ queryByTestId,
+ } = render(
+ wrapInTestApp(
+
+ {}}
+ />
+ ,
+ ),
+ );
+ await waitFor(() => !queryByTestId('progress'));
expect(getByText('title1')).toBeInTheDocument();
expect(getByText('title2')).toBeInTheDocument();
expect(getByText('person1')).toBeInTheDocument();
@@ -85,4 +120,26 @@ describe('Incidents', () => {
// assert links, mailto and hrefs, date calculation
expect(getAllByTitle('View in PagerDuty').length).toEqual(2);
});
+
+ it('Handle errors', async () => {
+ mockPagerDutyApi.getIncidentsByServiceId = jest
+ .fn()
+ .mockRejectedValueOnce(new Error('Error occurred'));
+
+ const { getByText, queryByTestId } = render(
+ wrapInTestApp(
+
+ {}}
+ />
+ ,
+ ),
+ );
+ await waitFor(() => !queryByTestId('progress'));
+ expect(
+ getByText('Error encountered while fetching information. Error occurred'),
+ ).toBeInTheDocument();
+ });
});
diff --git a/plugins/pagerduty/src/components/PagerDutyCard.test.tsx b/plugins/pagerduty/src/components/PagerDutyCard.test.tsx
new file mode 100644
index 0000000000..bca6f9f940
--- /dev/null
+++ b/plugins/pagerduty/src/components/PagerDutyCard.test.tsx
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 } from '@testing-library/react';
+import { PagerDutyCard } from './PagerdutyCard';
+import { Entity } from '@backstage/catalog-model';
+import { wrapInTestApp } from '@backstage/test-utils';
+import {
+ alertApiRef,
+ ApiProvider,
+ ApiRegistry,
+ createApiRef,
+} from '@backstage/core';
+import { pagerDutyApiRef, UnauthorizedError, PagerDutyClient } from '../api';
+import { Service } from './types';
+import { act } from 'react-dom/test-utils';
+
+const mockPagerDutyApi: Partial = {
+ getServiceByIntegrationKey: async () => [],
+ getOnCallByPolicyId: async () => [],
+ getIncidentsByServiceId: async () => [],
+};
+
+const apis = ApiRegistry.from([
+ [pagerDutyApiRef, mockPagerDutyApi],
+ [
+ alertApiRef,
+ createApiRef({
+ id: 'core.alert',
+ description: 'Used to report alerts and forward them to the app',
+ }),
+ ],
+]);
+const entity: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: {
+ name: 'pagerduty-test',
+ annotations: {
+ 'pagerduty.com/integration-key': 'abc123',
+ },
+ },
+};
+
+const service: Service = {
+ id: 'abc',
+ name: 'pagerduty-name',
+ html_url: 'www.example.com',
+ escalation_policy: {
+ id: 'def',
+ user: {
+ name: 'person1',
+ id: 'p1',
+ summary: 'person1',
+ email: 'person1@example.com',
+ html_url: 'http://a.com/id1',
+ },
+ },
+ integrationKey: 'abcd',
+};
+
+describe('PageDutyCard', () => {
+ it('Render pagerduty', async () => {
+ mockPagerDutyApi.getServiceByIntegrationKey = jest
+ .fn()
+ .mockImplementationOnce(async () => [service]);
+
+ const { getByText, queryByTestId } = render(
+ wrapInTestApp(
+
+
+ ,
+ ),
+ );
+ await waitFor(() => !queryByTestId('progress'));
+ expect(getByText('View in PagerDuty')).toBeInTheDocument();
+ expect(getByText('Trigger Alarm')).toBeInTheDocument();
+ expect(
+ getByText('Nice! No incidents are assigned to you!'),
+ ).toBeInTheDocument();
+ expect(getByText('Empty escalation policy')).toBeInTheDocument();
+ });
+
+ it('Handles custom error for missing token', async () => {
+ mockPagerDutyApi.getServiceByIntegrationKey = jest
+ .fn()
+ .mockRejectedValueOnce(new UnauthorizedError());
+
+ const { getByText, queryByTestId } = render(
+ wrapInTestApp(
+
+
+ ,
+ ),
+ );
+ await waitFor(() => !queryByTestId('progress'));
+ expect(getByText('Missing or invalid PagerDuty Token')).toBeInTheDocument();
+ });
+
+ it('handles general error', async () => {
+ mockPagerDutyApi.getServiceByIntegrationKey = jest
+ .fn()
+ .mockRejectedValueOnce(new Error('An error occurred'));
+ const { getByText, queryByTestId } = render(
+ wrapInTestApp(
+
+
+ ,
+ ),
+ );
+ 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.getServiceByIntegrationKey = jest
+ .fn()
+ .mockImplementationOnce(async () => [service]);
+
+ const { getByText, queryByTestId, getByTestId, getByRole } = render(
+ wrapInTestApp(
+
+
+ ,
+ ),
+ );
+ await waitFor(() => !queryByTestId('progress'));
+ expect(getByText('View in PagerDuty')).toBeInTheDocument();
+ expect(getByText('Trigger Alarm')).toBeInTheDocument();
+ const triggerButton = getByTestId('trigger-button');
+ await act(async () => {
+ fireEvent.click(triggerButton);
+ });
+ expect(getByRole('dialog')).toBeInTheDocument();
+ });
+});
diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx
index 7e26985136..440621dcf6 100644
--- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx
+++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx
@@ -25,9 +25,9 @@ import {
identityApiRef,
} from '@backstage/core';
import { pagerDutyApiRef } from '../../api';
-import { TriggerButton } from '../TriggerButton';
import { Entity } from '@backstage/catalog-model';
import { act } from 'react-dom/test-utils';
+import { TriggerDialog } from './TriggerDialog';
describe('TriggerDialog', () => {
const mockIdentityApi: Partial = {
@@ -36,7 +36,7 @@ describe('TriggerDialog', () => {
const mockTriggerAlarmFn = jest.fn();
const mockPagerDutyApi = {
- triggerPagerDutyAlarm: mockTriggerAlarmFn,
+ triggerAlarm: mockTriggerAlarmFn,
};
const apis = ApiRegistry.from([
@@ -63,24 +63,25 @@ describe('TriggerDialog', () => {
},
};
- const { getByText, getByRole, queryByRole, getByTestId } = render(
+ const { getByText, getByRole, getByTestId } = render(
wrapInTestApp(
-
+ {}}
+ name={entity.metadata.name}
+ integrationKey="abc123"
+ setShouldRefreshIncidents={() => {}}
+ />
,
),
);
- expect(queryByRole('dialog')).toBeNull();
- const alarmButton = getByText('Trigger Alarm');
- fireEvent.click(alarmButton);
+
expect(getByRole('dialog')).toBeInTheDocument();
expect(
- getByText(
- 'This action will send PagerDuty alarms and notifications to on-call people responsible for',
- {
- exact: false,
- },
- ),
+ getByText('This action will trigger an incident for ', {
+ exact: false,
+ }),
).toBeInTheDocument();
const input = getByTestId('trigger-input');
const description = 'Test Trigger Alarm';
From 6011b7d3e5476bf6c0e582cb132d282c434d872c Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Fri, 20 Nov 2020 12:07:43 +0100
Subject: [PATCH 29/52] add changeset
---
.changeset/great-spiders-repair.md | 7 +++++++
1 file changed, 7 insertions(+)
create mode 100644 .changeset/great-spiders-repair.md
diff --git a/.changeset/great-spiders-repair.md b/.changeset/great-spiders-repair.md
new file mode 100644
index 0000000000..0952612d84
--- /dev/null
+++ b/.changeset/great-spiders-repair.md
@@ -0,0 +1,7 @@
+---
+'example-app': patch
+'@backstage/plugin-catalog': patch
+'@backstage/plugin-pagerduty': patch
+---
+
+Added pagerduty plugin to example app
From 60b290cd0bcbe4e1fccf04897041836c3f051f0d Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Fri, 27 Nov 2020 11:10:04 +0100
Subject: [PATCH 30/52] use discoveryApi and update readme
---
plugins/pagerduty/README.md | 9 ++++-----
plugins/pagerduty/src/api/client.ts | 17 +++++++++++++----
2 files changed, 17 insertions(+), 9 deletions(-)
diff --git a/plugins/pagerduty/README.md b/plugins/pagerduty/README.md
index 9c5c0b2485..da37282aa7 100644
--- a/plugins/pagerduty/README.md
+++ b/plugins/pagerduty/README.md
@@ -47,18 +47,17 @@ import {
## Client configuration
-The PagerDutyClient can be configured with the appropriate urls:
+The client takes a config object which contains an instance of the `discoveryApi` which is used to look up the `baseUrl` of the `proxy`, and a `eventUrl` which is defaulted to: "https://events.pagerduty.com/v2".
In `apis.ts`:
```ts
createApiFactory({
api: pagerDutyApiRef,
- deps: { configApi: configApiRef },
- factory: ({ configApi }) =>
+ deps: { discoveryApi: discoveryApiRef },
+ factory: ({ discoveryApi }) =>
new PagerDutyClientApi({
- api_url: `https://api.pagerduty.com`,
- events_url: 'https://events.pagerduty.com/v2',
+ discoveryApi: discoveryApi
}),
}),
```
diff --git a/plugins/pagerduty/src/api/client.ts b/plugins/pagerduty/src/api/client.ts
index c9d40a1694..4ed994eec2 100644
--- a/plugins/pagerduty/src/api/client.ts
+++ b/plugins/pagerduty/src/api/client.ts
@@ -39,7 +39,9 @@ export class PagerDutyClientApi implements PagerDutyClient {
async getServiceByIntegrationKey(integrationKey: string): Promise {
const params = `include[]=integrations&include[]=escalation_policies&query=${integrationKey}`;
- const url = `${this.config.api_url}/services?${params}`;
+ const url = `${await this.config.discoveryApi.getBaseUrl(
+ 'proxy',
+ )}/pagerduty/services?${params}`;
const { services } = await this.getByUrl(url);
return services;
@@ -47,7 +49,9 @@ export class PagerDutyClientApi implements PagerDutyClient {
async getIncidentsByServiceId(serviceId: string): Promise {
const params = `service_ids[]=${serviceId}`;
- const url = `${this.config.api_url}/incidents?${params}`;
+ const url = `${await this.config.discoveryApi.getBaseUrl(
+ 'proxy',
+ )}/pagerduty/incidents?${params}`;
const { incidents } = await this.getByUrl(url);
return incidents;
@@ -55,7 +59,9 @@ export class PagerDutyClientApi implements PagerDutyClient {
async getOnCallByPolicyId(policyId: string): Promise {
const params = `include[]=users&escalation_policy_ids[]=${policyId}`;
- const url = `${this.config.api_url}/oncalls?${params}`;
+ const url = `${await this.config.discoveryApi.getBaseUrl(
+ 'proxy',
+ )}/pagerduty/oncalls?${params}`;
const { oncalls } = await this.getByUrl(url);
return oncalls;
@@ -92,7 +98,10 @@ export class PagerDutyClientApi implements PagerDutyClient {
body,
};
- return this.request(`${this.config.events_url}/enqueue`, options);
+ return this.request(
+ `${this.config.eventsUrl ?? 'https://events.pagerduty.com/v2'}/enqueue`,
+ options,
+ );
}
private async getByUrl(url: string): Promise {
From aa88559488d9070f93471a716b235ab07c55eb3e Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Fri, 27 Nov 2020 11:24:19 +0100
Subject: [PATCH 31/52] use discoveryApi
---
plugins/pagerduty/src/components/types.tsx | 6 ++++--
plugins/pagerduty/src/plugin.ts | 11 ++++-------
2 files changed, 8 insertions(+), 9 deletions(-)
diff --git a/plugins/pagerduty/src/components/types.tsx b/plugins/pagerduty/src/components/types.tsx
index e968656e65..5e5ee0a61b 100644
--- a/plugins/pagerduty/src/components/types.tsx
+++ b/plugins/pagerduty/src/components/types.tsx
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+import { DiscoveryApi } from '@backstage/core';
+
export type Incident = {
id: string;
title: string;
@@ -76,6 +78,6 @@ export type RequestOptions = {
};
export type ClientApiConfig = {
- api_url: string;
- events_url: string;
+ eventsUrl?: string;
+ discoveryApi: DiscoveryApi;
};
diff --git a/plugins/pagerduty/src/plugin.ts b/plugins/pagerduty/src/plugin.ts
index 2e07b5c430..02b11ed773 100644
--- a/plugins/pagerduty/src/plugin.ts
+++ b/plugins/pagerduty/src/plugin.ts
@@ -17,7 +17,7 @@ import {
createApiFactory,
createPlugin,
createRouteRef,
- configApiRef,
+ discoveryApiRef,
} from '@backstage/core';
import { pagerDutyApiRef, PagerDutyClientApi } from './api';
@@ -31,13 +31,10 @@ export const plugin = createPlugin({
apis: [
createApiFactory({
api: pagerDutyApiRef,
- deps: { configApi: configApiRef },
- factory: ({ configApi }) =>
+ deps: { discoveryApi: discoveryApiRef },
+ factory: ({ discoveryApi }) =>
new PagerDutyClientApi({
- api_url: `${configApi.getString(
- 'backend.baseUrl',
- )}/api/proxy/pagerduty`,
- events_url: 'https://events.pagerduty.com/v2',
+ discoveryApi: discoveryApi,
}),
}),
],
From ac6e65aa1ae4bcb280e5a8d76f8cf00f76430bd4 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Fri, 27 Nov 2020 14:10:17 +0100
Subject: [PATCH 32/52] create MissingTokenError and flatten error
---
.../src/components/MissingTokenError.tsx | 35 +++++++++++++++++++
.../src/components/PagerdutyCard.tsx | 28 ++++-----------
2 files changed, 42 insertions(+), 21 deletions(-)
create mode 100644 plugins/pagerduty/src/components/MissingTokenError.tsx
diff --git a/plugins/pagerduty/src/components/MissingTokenError.tsx b/plugins/pagerduty/src/components/MissingTokenError.tsx
new file mode 100644
index 0000000000..c22552b7c6
--- /dev/null
+++ b/plugins/pagerduty/src/components/MissingTokenError.tsx
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { EmptyState } from '@backstage/core';
+import { Button } from '@material-ui/core';
+
+export const MissingTokenError = () => (
+
+ Read More
+
+ }
+ />
+);
diff --git a/plugins/pagerduty/src/components/PagerdutyCard.tsx b/plugins/pagerduty/src/components/PagerdutyCard.tsx
index f24e91a9e0..22a471c031 100644
--- a/plugins/pagerduty/src/components/PagerdutyCard.tsx
+++ b/plugins/pagerduty/src/components/PagerdutyCard.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import React, { useState } from 'react';
-import { useApi, EmptyState } from '@backstage/core';
+import { useApi } from '@backstage/core';
import { Entity } from '@backstage/catalog-model';
import {
Button,
@@ -31,9 +31,10 @@ import { useAsync } from 'react-use';
import { Alert } from '@material-ui/lab';
import { pagerDutyApiRef, UnauthorizedError } from '../api';
import { IconLinkVertical } from '@backstage/plugin-catalog';
-import PagerDutyIcon from './PagerDutyIcon';
+import { PagerDutyIcon } from './PagerDutyIcon';
import AlarmAddIcon from '@material-ui/icons/AlarmAdd';
import { TriggerDialog } from './TriggerDialog';
+import { MissingTokenError } from './MissingTokenError';
const useStyles = makeStyles(theme => ({
links: {
@@ -93,26 +94,11 @@ export const PagerDutyCard = ({ entity }: Props) => {
setShowDialog(!showDialog);
};
- if (error) {
- if (error instanceof UnauthorizedError) {
- return (
-
- Read More
-
- }
- />
- );
- }
+ if (error instanceof UnauthorizedError) {
+ return ;
+ }
+ if (error) {
return (
Error encountered while fetching information. {error.message}
From a57ed028f85b4beda64830a2f17b14254be9b7e2 Mon Sep 17 00:00:00 2001
From: samiramkr
Date: Fri, 27 Nov 2020 14:49:51 +0100
Subject: [PATCH 33/52] Update plugins/pagerduty/README.md
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Fredrik Adelöw
---
plugins/pagerduty/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/plugins/pagerduty/README.md b/plugins/pagerduty/README.md
index da37282aa7..b3e0b9f4e6 100644
--- a/plugins/pagerduty/README.md
+++ b/plugins/pagerduty/README.md
@@ -2,7 +2,7 @@
## Overview
-This plugin displays PagerDuty information about an entity such as if there are any active incidents and how the escalation policy looks like.
+This plugin displays PagerDuty information about an entity such as if there are any active incidents and what the escalation policy is.
There is also an easy way to trigger an alarm directly to the person who is currently on-call.
From 43d0967db89e2cf32808d0ae4a02ed442a27c755 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Fri, 27 Nov 2020 16:10:55 +0100
Subject: [PATCH 34/52] refactor code
---
.../AboutCard/IconLinkVertical/IconLinkVertical.tsx | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx b/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx
index 7e1dc61fa7..4a8bf2a0ab 100644
--- a/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx
+++ b/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx
@@ -75,11 +75,11 @@ export function IconLinkVertical({
if (action) {
return (
From b2013cc40b33ec4d43138c4d02e553c5b142b981 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Fri, 27 Nov 2020 16:35:53 +0100
Subject: [PATCH 35/52] refactor type
---
plugins/pagerduty/src/api/client.ts | 14 +++++++-------
plugins/pagerduty/src/api/types.ts | 14 ++++++++------
2 files changed, 15 insertions(+), 13 deletions(-)
diff --git a/plugins/pagerduty/src/api/client.ts b/plugins/pagerduty/src/api/client.ts
index 4ed994eec2..7b453b7298 100644
--- a/plugins/pagerduty/src/api/client.ts
+++ b/plugins/pagerduty/src/api/client.ts
@@ -25,7 +25,7 @@ import {
OnCallsResponse,
OnCall,
} from '../components/types';
-import { PagerDutyClient } from './types';
+import { PagerDutyClient, TriggerAlarmRequest } from './types';
export class UnauthorizedError extends Error {}
@@ -67,12 +67,12 @@ export class PagerDutyClientApi implements PagerDutyClient {
return oncalls;
}
- triggerAlarm(
- integrationKey: string,
- source: string,
- description: string,
- userName: string,
- ) {
+ triggerAlarm({
+ integrationKey,
+ source,
+ description,
+ userName,
+ }: TriggerAlarmRequest): Promise {
const body = JSON.stringify({
event_action: 'trigger',
routing_key: integrationKey,
diff --git a/plugins/pagerduty/src/api/types.ts b/plugins/pagerduty/src/api/types.ts
index c07834a213..17c2067818 100644
--- a/plugins/pagerduty/src/api/types.ts
+++ b/plugins/pagerduty/src/api/types.ts
@@ -16,6 +16,13 @@
import { Incident, OnCall, Service } from '../components/types';
+export type TriggerAlarmRequest = {
+ integrationKey: string;
+ source: string;
+ description: string;
+ userName: string;
+};
+
export interface PagerDutyClient {
/**
* Fetches a list of services, filtered by the provided integration key.
@@ -38,10 +45,5 @@ export interface PagerDutyClient {
/**
* Triggers an incident to whoever is on-call.
*/
- triggerAlarm(
- integrationKey: string,
- source: string,
- description: string,
- userName: string,
- ): Promise;
+ triggerAlarm(request: TriggerAlarmRequest): Promise;
}
From 6a02802d728031f19550b3cf26203e29b3e7008b Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Fri, 27 Nov 2020 16:59:16 +0100
Subject: [PATCH 36/52] use Progress
---
.../src/components/Escalation/EscalationPolicy.tsx | 14 ++++++++------
.../src/components/Incident/Incidents.tsx | 14 ++++++++------
plugins/pagerduty/src/components/PagerdutyCard.tsx | 4 ++--
plugins/pagerduty/src/setupTests.ts | 2 --
4 files changed, 18 insertions(+), 16 deletions(-)
diff --git a/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx b/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx
index ccf70abb0b..5e67d52172 100644
--- a/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx
+++ b/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx
@@ -15,12 +15,12 @@
*/
import React from 'react';
-import { List, ListSubheader, LinearProgress } from '@material-ui/core';
+import { List, ListSubheader } from '@material-ui/core';
import { EscalationUsersEmptyState } from './EscalationUsersEmptyState';
import { EscalationUser } from './EscalationUser';
import { useAsync } from 'react-use';
import { pagerDutyApiRef } from '../../api';
-import { useApi } from '@backstage/core';
+import { useApi, Progress } from '@backstage/core';
import { Alert } from '@material-ui/lab';
type Props = {
@@ -46,16 +46,18 @@ export const EscalationPolicy = ({ policyId }: Props) => {
}
if (loading) {
- return ;
+ return ;
}
- return users!.length ? (
+ if (!users?.length) {
+ return ;
+ }
+
+ return (
ON CALL}>
{users!.map((user, index) => (
))}
- ) : (
-
);
};
diff --git a/plugins/pagerduty/src/components/Incident/Incidents.tsx b/plugins/pagerduty/src/components/Incident/Incidents.tsx
index 2d2e5b01fd..1d4ec3a1a6 100644
--- a/plugins/pagerduty/src/components/Incident/Incidents.tsx
+++ b/plugins/pagerduty/src/components/Incident/Incidents.tsx
@@ -15,12 +15,12 @@
*/
import React, { useEffect } from 'react';
-import { List, ListSubheader, LinearProgress } from '@material-ui/core';
+import { List, ListSubheader } from '@material-ui/core';
import { IncidentListItem } from './IncidentListItem';
import { IncidentsEmptyState } from './IncidentEmptyState';
import { useAsyncFn } from 'react-use';
import { pagerDutyApiRef } from '../../api';
-import { useApi } from '@backstage/core';
+import { useApi, Progress } from '@backstage/core';
import { Alert } from '@material-ui/lab';
type Props = {
@@ -56,16 +56,18 @@ export const Incidents = ({
}
if (loading) {
- return ;
+ return ;
}
- return incidents?.length ? (
+ if (!incidents?.length) {
+ return ;
+ }
+
+ return (
INCIDENTS}>
{incidents!.map((incident, index) => (
))}
- ) : (
-
);
};
diff --git a/plugins/pagerduty/src/components/PagerdutyCard.tsx b/plugins/pagerduty/src/components/PagerdutyCard.tsx
index 22a471c031..333e042f82 100644
--- a/plugins/pagerduty/src/components/PagerdutyCard.tsx
+++ b/plugins/pagerduty/src/components/PagerdutyCard.tsx
@@ -22,7 +22,6 @@ import {
CardContent,
CardHeader,
Divider,
- LinearProgress,
makeStyles,
} from '@material-ui/core';
import { Incidents } from './Incident';
@@ -35,6 +34,7 @@ import { PagerDutyIcon } from './PagerDutyIcon';
import AlarmAddIcon from '@material-ui/icons/AlarmAdd';
import { TriggerDialog } from './TriggerDialog';
import { MissingTokenError } from './MissingTokenError';
+import { Progress } from '@backstage/core';
const useStyles = makeStyles(theme => ({
links: {
@@ -107,7 +107,7 @@ export const PagerDutyCard = ({ entity }: Props) => {
}
if (loading) {
- return ;
+ return ;
}
const pagerdutyLink = {
diff --git a/plugins/pagerduty/src/setupTests.ts b/plugins/pagerduty/src/setupTests.ts
index d7857386de..0bfa67b49a 100644
--- a/plugins/pagerduty/src/setupTests.ts
+++ b/plugins/pagerduty/src/setupTests.ts
@@ -14,5 +14,3 @@
* limitations under the License.
*/
import '@testing-library/jest-dom';
-
-global.fetch = require('node-fetch');
From d2f716d64083ad4769153bb6dd833d5e9b093604 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Mon, 30 Nov 2020 10:57:16 +0100
Subject: [PATCH 37/52] fix changeset
---
.changeset/great-spiders-repair.md | 1 -
1 file changed, 1 deletion(-)
diff --git a/.changeset/great-spiders-repair.md b/.changeset/great-spiders-repair.md
index 0952612d84..2cf1ddd8e8 100644
--- a/.changeset/great-spiders-repair.md
+++ b/.changeset/great-spiders-repair.md
@@ -1,5 +1,4 @@
---
-'example-app': patch
'@backstage/plugin-catalog': patch
'@backstage/plugin-pagerduty': patch
---
From 49938595bcc8b54a0074bc8d34a8c8b5b4f94382 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Mon, 30 Nov 2020 18:31:35 +0100
Subject: [PATCH 38/52] improve updating incident list, refactor code, update
tests, move types, rename types file
---
plugins/pagerduty/README.md | 3 ++-
plugins/pagerduty/src/api/client.ts | 13 +++++-----
plugins/pagerduty/src/api/types.ts | 24 ++++++++++++++++++
.../components/Escalation/EscalationUser.tsx | 4 +--
.../components/Incident/IncidentListItem.tsx | 4 +--
.../components/Incident/Incidents.test.tsx | 6 ++---
.../src/components/Incident/Incidents.tsx | 12 +++------
.../src/components/PagerDutyIcon.tsx | 4 +--
.../src/components/PagerdutyCard.tsx | 16 ++++++------
.../TriggerDialog/TriggerDialog.test.tsx | 14 ++++++-----
.../TriggerDialog/TriggerDialog.tsx | 24 +++++++++---------
.../src/components/{types.tsx => types.ts} | 25 -------------------
12 files changed, 71 insertions(+), 78 deletions(-)
rename plugins/pagerduty/src/components/{types.tsx => types.ts} (74%)
diff --git a/plugins/pagerduty/README.md b/plugins/pagerduty/README.md
index b3e0b9f4e6..7a886627e3 100644
--- a/plugins/pagerduty/README.md
+++ b/plugins/pagerduty/README.md
@@ -57,7 +57,8 @@ createApiFactory({
deps: { discoveryApi: discoveryApiRef },
factory: ({ discoveryApi }) =>
new PagerDutyClientApi({
- discoveryApi: discoveryApi
+ discoveryApi: discoveryApi,
+ eventUrl: "https://events.pagerduty.com/v2" //to override the default value
}),
}),
```
diff --git a/plugins/pagerduty/src/api/client.ts b/plugins/pagerduty/src/api/client.ts
index 7b453b7298..ab65514466 100644
--- a/plugins/pagerduty/src/api/client.ts
+++ b/plugins/pagerduty/src/api/client.ts
@@ -15,17 +15,16 @@
*/
import { createApiRef } from '@backstage/core';
+import { Service, Incident, OnCall } from '../components/types';
import {
- Service,
- Incident,
- RequestOptions,
- ClientApiConfig,
+ PagerDutyClient,
+ TriggerAlarmRequest,
ServicesResponse,
IncidentsResponse,
OnCallsResponse,
- OnCall,
-} from '../components/types';
-import { PagerDutyClient, TriggerAlarmRequest } from './types';
+ ClientApiConfig,
+ RequestOptions,
+} from './types';
export class UnauthorizedError extends Error {}
diff --git a/plugins/pagerduty/src/api/types.ts b/plugins/pagerduty/src/api/types.ts
index 17c2067818..2231cc6991 100644
--- a/plugins/pagerduty/src/api/types.ts
+++ b/plugins/pagerduty/src/api/types.ts
@@ -15,6 +15,7 @@
*/
import { Incident, OnCall, Service } from '../components/types';
+import { DiscoveryApi } from '@backstage/core';
export type TriggerAlarmRequest = {
integrationKey: string;
@@ -47,3 +48,26 @@ export interface PagerDutyClient {
*/
triggerAlarm(request: TriggerAlarmRequest): Promise;
}
+
+export type ServicesResponse = {
+ services: Service[];
+};
+
+export type IncidentsResponse = {
+ incidents: Incident[];
+};
+
+export type OnCallsResponse = {
+ oncalls: OnCall[];
+};
+
+export type ClientApiConfig = {
+ eventsUrl?: string;
+ discoveryApi: DiscoveryApi;
+};
+
+export type RequestOptions = {
+ method: string;
+ headers: HeadersInit;
+ body?: BodyInit;
+};
diff --git a/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx b/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx
index 642c9ecd96..3847896e89 100644
--- a/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx
+++ b/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx
@@ -27,7 +27,7 @@ import {
} from '@material-ui/core';
import Avatar from '@material-ui/core/Avatar';
import EmailIcon from '@material-ui/icons/Email';
-import PagerdutyIcon from '../PagerDutyIcon';
+import { PagerDutyIcon } from '../PagerDutyIcon';
import { User } from '../types';
const useStyles = makeStyles({
@@ -69,7 +69,7 @@ export const EscalationUser = ({ user }: Props) => {
rel="noopener noreferrer"
color="primary"
>
-
+
diff --git a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx
index 7a97dad281..488631b834 100644
--- a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx
+++ b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx
@@ -29,7 +29,7 @@ import {
import { StatusError, StatusWarning } from '@backstage/core';
import moment from 'moment';
import { Incident } from '../types';
-import PagerdutyIcon from '../PagerDutyIcon';
+import { PagerDutyIcon } from '../PagerDutyIcon';
const useStyles = makeStyles({
denseListIcon: {
@@ -96,7 +96,7 @@ export const IncidentListItem = ({ incident }: Props) => {
rel="noopener noreferrer"
color="primary"
>
-
+
diff --git a/plugins/pagerduty/src/components/Incident/Incidents.test.tsx b/plugins/pagerduty/src/components/Incident/Incidents.test.tsx
index 6baa410021..3eef24cc6a 100644
--- a/plugins/pagerduty/src/components/Incident/Incidents.test.tsx
+++ b/plugins/pagerduty/src/components/Incident/Incidents.test.tsx
@@ -37,7 +37,7 @@ describe('Incidents', () => {
{}}
/>
,
@@ -101,7 +101,7 @@ describe('Incidents', () => {
{}}
/>
,
@@ -131,7 +131,7 @@ describe('Incidents', () => {
{}}
/>
,
diff --git a/plugins/pagerduty/src/components/Incident/Incidents.tsx b/plugins/pagerduty/src/components/Incident/Incidents.tsx
index 1d4ec3a1a6..4b229fb4bc 100644
--- a/plugins/pagerduty/src/components/Incident/Incidents.tsx
+++ b/plugins/pagerduty/src/components/Incident/Incidents.tsx
@@ -25,27 +25,21 @@ import { Alert } from '@material-ui/lab';
type Props = {
serviceId: string;
- shouldRefreshIncidents: boolean;
- setShouldRefreshIncidents: (shouldRefreshIncidents: boolean) => void;
+ refreshIncidents: Boolean;
};
-export const Incidents = ({
- serviceId,
- shouldRefreshIncidents,
- setShouldRefreshIncidents,
-}: Props) => {
+export const Incidents = ({ serviceId, refreshIncidents }: Props) => {
const api = useApi(pagerDutyApiRef);
const [{ value: incidents, loading, error }, getIncidents] = useAsyncFn(
async () => {
- setShouldRefreshIncidents(false);
return await api.getIncidentsByServiceId(serviceId);
},
);
useEffect(() => {
getIncidents();
- }, [shouldRefreshIncidents, getIncidents]);
+ }, [refreshIncidents, getIncidents]);
if (error) {
return (
diff --git a/plugins/pagerduty/src/components/PagerDutyIcon.tsx b/plugins/pagerduty/src/components/PagerDutyIcon.tsx
index 36f6e1989d..67cfc0036a 100644
--- a/plugins/pagerduty/src/components/PagerDutyIcon.tsx
+++ b/plugins/pagerduty/src/components/PagerDutyIcon.tsx
@@ -17,7 +17,7 @@
import React from 'react';
import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon';
-const SvgPd = (props: SvgIconProps) =>
+export const PagerDutyIcon = (props: SvgIconProps) =>
React.createElement(
SvgIcon,
props,
@@ -25,5 +25,3 @@ const SvgPd = (props: SvgIconProps) =>
,
);
-
-export default SvgPd;
diff --git a/plugins/pagerduty/src/components/PagerdutyCard.tsx b/plugins/pagerduty/src/components/PagerdutyCard.tsx
index 333e042f82..ac309e8a95 100644
--- a/plugins/pagerduty/src/components/PagerdutyCard.tsx
+++ b/plugins/pagerduty/src/components/PagerdutyCard.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import React, { useState } from 'react';
-import { useApi } from '@backstage/core';
+import { useApi, Progress } from '@backstage/core';
import { Entity } from '@backstage/catalog-model';
import {
Button,
@@ -34,7 +34,6 @@ import { PagerDutyIcon } from './PagerDutyIcon';
import AlarmAddIcon from '@material-ui/icons/AlarmAdd';
import { TriggerDialog } from './TriggerDialog';
import { MissingTokenError } from './MissingTokenError';
-import { Progress } from '@backstage/core';
const useStyles = makeStyles(theme => ({
links: {
@@ -72,9 +71,7 @@ export const PagerDutyCard = ({ entity }: Props) => {
const classes = useStyles();
const api = useApi(pagerDutyApiRef);
const [showDialog, setShowDialog] = useState(false);
- const [shouldRefreshIncidents, setShouldRefreshIncidents] = useState(
- false,
- );
+ const [refreshIncidents, setRefreshIncidents] = useState(false);
const integrationKey = entity.metadata.annotations![
PAGERDUTY_INTEGRATION_KEY
];
@@ -129,6 +126,10 @@ export const PagerDutyCard = ({ entity }: Props) => {
),
};
+ const onTriggerRefresh = () => {
+ setRefreshIncidents(true);
+ };
+
return (
{
{
handleDialog={handleDialog}
name={entity.metadata.name}
integrationKey={integrationKey}
- setShouldRefreshIncidents={setShouldRefreshIncidents}
+ onTriggerRefresh={onTriggerRefresh}
/>
diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx
index 440621dcf6..3d2a48b419 100644
--- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx
+++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx
@@ -71,7 +71,7 @@ describe('TriggerDialog', () => {
handleDialog={() => {}}
name={entity.metadata.name}
integrationKey="abc123"
- setShouldRefreshIncidents={() => {}}
+ onTriggerRefresh={() => {}}
/>
,
),
@@ -93,11 +93,13 @@ describe('TriggerDialog', () => {
fireEvent.click(triggerButton);
});
expect(mockTriggerAlarmFn).toHaveBeenCalled();
- expect(mockTriggerAlarmFn).toHaveBeenCalledWith(
- entity!.metadata!.annotations!['pagerduty.com/integration-key'],
- window.location.toString(),
+ expect(mockTriggerAlarmFn).toHaveBeenCalledWith({
+ integrationKey: entity!.metadata!.annotations![
+ 'pagerduty.com/integration-key'
+ ],
+ source: window.location.toString(),
description,
- 'guest@example.com',
- );
+ userName: 'guest@example.com',
+ });
});
});
diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx
index e6bb6d107d..4717b8fece 100644
--- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx
+++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx
@@ -35,7 +35,7 @@ type Props = {
integrationKey: string;
showDialog: boolean;
handleDialog: () => void;
- setShouldRefreshIncidents: (shouldRefreshIncidents: boolean) => void;
+ onTriggerRefresh: () => void;
};
export const TriggerDialog = ({
@@ -43,22 +43,22 @@ export const TriggerDialog = ({
integrationKey,
showDialog,
handleDialog,
- setShouldRefreshIncidents,
+ onTriggerRefresh,
}: Props) => {
const alertApi = useApi(alertApiRef);
const identityApi = useApi(identityApiRef);
- const userId = identityApi.getUserId();
+ const userName = identityApi.getUserId();
const api = useApi(pagerDutyApiRef);
const [description, setDescription] = useState('');
const [{ value, loading, error }, handleTriggerAlarm] = useAsyncFn(
- async (desc: string) =>
- await api.triggerAlarm(
+ async (description: string) =>
+ await api.triggerAlarm({
integrationKey,
- window.location.toString(),
- desc,
- userId,
- ),
+ source: window.location.toString(),
+ description,
+ userName,
+ }),
);
const descriptionChanged = (
@@ -70,9 +70,9 @@ export const TriggerDialog = ({
useEffect(() => {
if (value) {
alertApi.post({
- message: `Alarm successfully triggered by ${userId}`,
+ message: `Alarm successfully triggered by ${userName}`,
});
- setShouldRefreshIncidents(true);
+ onTriggerRefresh();
handleDialog();
}
@@ -82,7 +82,7 @@ export const TriggerDialog = ({
severity: 'error',
});
}
- }, [value, error, alertApi, handleDialog, userId, setShouldRefreshIncidents]);
+ }, [value, error, alertApi, handleDialog, userName, onTriggerRefresh]);
if (!showDialog) {
return null;
diff --git a/plugins/pagerduty/src/components/types.tsx b/plugins/pagerduty/src/components/types.ts
similarity index 74%
rename from plugins/pagerduty/src/components/types.tsx
rename to plugins/pagerduty/src/components/types.ts
index 5e5ee0a61b..a93791fe8e 100644
--- a/plugins/pagerduty/src/components/types.tsx
+++ b/plugins/pagerduty/src/components/types.ts
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-import { DiscoveryApi } from '@backstage/core';
-
export type Incident = {
id: string;
title: string;
@@ -58,26 +56,3 @@ export type User = {
html_url: string;
name: string;
};
-
-export type ServicesResponse = {
- services: Service[];
-};
-
-export type IncidentsResponse = {
- incidents: Incident[];
-};
-
-export type OnCallsResponse = {
- oncalls: OnCall[];
-};
-
-export type RequestOptions = {
- method: string;
- headers: HeadersInit;
- body?: BodyInit;
-};
-
-export type ClientApiConfig = {
- eventsUrl?: string;
- discoveryApi: DiscoveryApi;
-};
From 04a53de52fdd9424d35bd398904ff21989853153 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Tue, 1 Dec 2020 13:46:46 +0100
Subject: [PATCH 39/52] rename type and class, fix test
---
plugins/pagerduty/README.md | 2 +-
plugins/pagerduty/src/api/client.ts | 6 +++---
plugins/pagerduty/src/api/index.ts | 8 ++------
plugins/pagerduty/src/api/types.ts | 2 +-
.../src/components/Incident/Incidents.test.tsx | 18 +++---------------
.../src/components/PagerDutyCard.test.tsx | 2 +-
plugins/pagerduty/src/index.ts | 4 ++--
plugins/pagerduty/src/plugin.ts | 4 ++--
8 files changed, 15 insertions(+), 31 deletions(-)
diff --git a/plugins/pagerduty/README.md b/plugins/pagerduty/README.md
index 7a886627e3..b925e9d7a7 100644
--- a/plugins/pagerduty/README.md
+++ b/plugins/pagerduty/README.md
@@ -56,7 +56,7 @@ createApiFactory({
api: pagerDutyApiRef,
deps: { discoveryApi: discoveryApiRef },
factory: ({ discoveryApi }) =>
- new PagerDutyClientApi({
+ new PagerDutyClient({
discoveryApi: discoveryApi,
eventUrl: "https://events.pagerduty.com/v2" //to override the default value
}),
diff --git a/plugins/pagerduty/src/api/client.ts b/plugins/pagerduty/src/api/client.ts
index ab65514466..214e7d57a7 100644
--- a/plugins/pagerduty/src/api/client.ts
+++ b/plugins/pagerduty/src/api/client.ts
@@ -17,7 +17,7 @@
import { createApiRef } from '@backstage/core';
import { Service, Incident, OnCall } from '../components/types';
import {
- PagerDutyClient,
+ PagerDutyApi,
TriggerAlarmRequest,
ServicesResponse,
IncidentsResponse,
@@ -28,12 +28,12 @@ import {
export class UnauthorizedError extends Error {}
-export const pagerDutyApiRef = createApiRef({
+export const pagerDutyApiRef = createApiRef({
id: 'plugin.pagerduty.api',
description: 'Used to fetch data from PagerDuty API',
});
-export class PagerDutyClientApi implements PagerDutyClient {
+export class PagerDutyClient implements PagerDutyApi {
constructor(private readonly config: ClientApiConfig) {}
async getServiceByIntegrationKey(integrationKey: string): Promise {
diff --git a/plugins/pagerduty/src/api/index.ts b/plugins/pagerduty/src/api/index.ts
index 5ac7470475..90604c4012 100644
--- a/plugins/pagerduty/src/api/index.ts
+++ b/plugins/pagerduty/src/api/index.ts
@@ -14,9 +14,5 @@
* limitations under the License.
*/
-export {
- PagerDutyClientApi,
- pagerDutyApiRef,
- UnauthorizedError,
-} from './client';
-export type { PagerDutyClient } from './types';
+export { PagerDutyClient, pagerDutyApiRef, UnauthorizedError } from './client';
+export type { PagerDutyApi } from './types';
diff --git a/plugins/pagerduty/src/api/types.ts b/plugins/pagerduty/src/api/types.ts
index 2231cc6991..4b3ae74cc1 100644
--- a/plugins/pagerduty/src/api/types.ts
+++ b/plugins/pagerduty/src/api/types.ts
@@ -24,7 +24,7 @@ export type TriggerAlarmRequest = {
userName: string;
};
-export interface PagerDutyClient {
+export interface PagerDutyApi {
/**
* Fetches a list of services, filtered by the provided integration key.
*
diff --git a/plugins/pagerduty/src/components/Incident/Incidents.test.tsx b/plugins/pagerduty/src/components/Incident/Incidents.test.tsx
index 3eef24cc6a..3c5e30e0cc 100644
--- a/plugins/pagerduty/src/components/Incident/Incidents.test.tsx
+++ b/plugins/pagerduty/src/components/Incident/Incidents.test.tsx
@@ -35,11 +35,7 @@ describe('Incidents', () => {
const { getByText, queryByTestId } = render(
wrapInTestApp(
- {}}
- />
+
,
),
);
@@ -99,11 +95,7 @@ describe('Incidents', () => {
} = render(
wrapInTestApp(
- {}}
- />
+
,
),
);
@@ -129,11 +121,7 @@ describe('Incidents', () => {
const { getByText, queryByTestId } = render(
wrapInTestApp(
- {}}
- />
+
,
),
);
diff --git a/plugins/pagerduty/src/components/PagerDutyCard.test.tsx b/plugins/pagerduty/src/components/PagerDutyCard.test.tsx
index bca6f9f940..26d67a55e4 100644
--- a/plugins/pagerduty/src/components/PagerDutyCard.test.tsx
+++ b/plugins/pagerduty/src/components/PagerDutyCard.test.tsx
@@ -15,7 +15,7 @@
*/
import React from 'react';
import { render, waitFor, fireEvent } from '@testing-library/react';
-import { PagerDutyCard } from './PagerdutyCard';
+import { PagerDutyCard } from './PagerDutyCard';
import { Entity } from '@backstage/catalog-model';
import { wrapInTestApp } from '@backstage/test-utils';
import {
diff --git a/plugins/pagerduty/src/index.ts b/plugins/pagerduty/src/index.ts
index 1e8e549ac9..4ecd4edcc6 100644
--- a/plugins/pagerduty/src/index.ts
+++ b/plugins/pagerduty/src/index.ts
@@ -17,9 +17,9 @@ export { plugin } from './plugin';
export {
isPluginApplicableToEntity,
PagerDutyCard,
-} from './components/PagerdutyCard';
+} from './components/PagerDutyCard';
export {
- PagerDutyClientApi,
+ PagerDutyClient,
pagerDutyApiRef,
UnauthorizedError,
} from './api/client';
diff --git a/plugins/pagerduty/src/plugin.ts b/plugins/pagerduty/src/plugin.ts
index 02b11ed773..4e8f429b05 100644
--- a/plugins/pagerduty/src/plugin.ts
+++ b/plugins/pagerduty/src/plugin.ts
@@ -19,7 +19,7 @@ import {
createRouteRef,
discoveryApiRef,
} from '@backstage/core';
-import { pagerDutyApiRef, PagerDutyClientApi } from './api';
+import { pagerDutyApiRef, PagerDutyClient } from './api';
export const rootRouteRef = createRouteRef({
path: '/pagerduty',
@@ -33,7 +33,7 @@ export const plugin = createPlugin({
api: pagerDutyApiRef,
deps: { discoveryApi: discoveryApiRef },
factory: ({ discoveryApi }) =>
- new PagerDutyClientApi({
+ new PagerDutyClient({
discoveryApi: discoveryApi,
}),
}),
From 3d392a0b6acfe0852d8c7eb83200dc8d276e5a48 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Tue, 1 Dec 2020 16:38:40 +0100
Subject: [PATCH 40/52] address comments
---
app-config.yaml | 2 ++
packages/app/package.json | 6 +++---
packages/app/src/plugins.ts | 2 +-
plugins/pagerduty/README.md | 17 +++++------------
plugins/pagerduty/src/api/client.ts | 14 ++++++++++++--
plugins/pagerduty/src/api/types.ts | 2 +-
.../src/components/PagerDutyCard.test.tsx | 3 +--
.../TriggerDialog/TriggerDialog.test.tsx | 3 +--
plugins/pagerduty/src/plugin.ts | 9 ++++-----
9 files changed, 30 insertions(+), 28 deletions(-)
diff --git a/app-config.yaml b/app-config.yaml
index 18eeb24400..e80b5df021 100644
--- a/app-config.yaml
+++ b/app-config.yaml
@@ -312,3 +312,5 @@ homepage:
timezone: 'Europe/Stockholm'
- label: TYO
timezone: 'Asia/Tokyo'
+pagerduty:
+ eventsBaseUrl: 'https://events.pagerduty.com/v2'
diff --git a/packages/app/package.json b/packages/app/package.json
index b0499c8431..f78a3474ff 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -21,25 +21,25 @@
"@backstage/plugin-kubernetes": "^0.3.0",
"@backstage/plugin-lighthouse": "^0.2.3",
"@backstage/plugin-newrelic": "^0.2.1",
+ "@backstage/plugin-pagerduty": "0.2.1",
"@backstage/plugin-register-component": "^0.2.2",
"@backstage/plugin-rollbar": "^0.2.3",
"@backstage/plugin-scaffolder": "^0.3.1",
- "@backstage/plugin-sentry": "^0.2.3",
"@backstage/plugin-search": "^0.2.1",
+ "@backstage/plugin-sentry": "^0.2.3",
"@backstage/plugin-tech-radar": "^0.3.0",
"@backstage/plugin-techdocs": "^0.2.3",
"@backstage/plugin-user-settings": "^0.2.2",
"@backstage/plugin-welcome": "^0.2.1",
"@backstage/test-utils": "^0.1.3",
- "@backstage/plugin-pagerduty": "^0.2.1",
"@backstage/theme": "^0.2.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@octokit/rest": "^18.0.0",
+ "@roadiehq/backstage-plugin-buildkite": "^0.1.3",
"@roadiehq/backstage-plugin-github-insights": "^0.2.15",
"@roadiehq/backstage-plugin-github-pull-requests": "^0.6.3",
"@roadiehq/backstage-plugin-travis-ci": "^0.2.8",
- "@roadiehq/backstage-plugin-buildkite": "^0.1.3",
"history": "^5.0.0",
"prop-types": "^15.7.2",
"react": "^16.12.0",
diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts
index 3fdf236e8d..d70694969e 100644
--- a/packages/app/src/plugins.ts
+++ b/packages/app/src/plugins.ts
@@ -38,6 +38,6 @@ export { plugin as Cloudbuild } from '@backstage/plugin-cloudbuild';
export { plugin as CostInsights } from '@backstage/plugin-cost-insights';
export { plugin as GitHubInsights } from '@roadiehq/backstage-plugin-github-insights';
export { plugin as UserSettings } from '@backstage/plugin-user-settings';
-export { plugin as Pagerduty } from '@backstage/plugin-pagerduty';
+export { plugin as PagerDuty } from '@backstage/plugin-pagerduty';
export { plugin as Buildkite } from '@roadiehq/backstage-plugin-buildkite';
export { plugin as Search } from '@backstage/plugin-search';
diff --git a/plugins/pagerduty/README.md b/plugins/pagerduty/README.md
index b925e9d7a7..1c22053b88 100644
--- a/plugins/pagerduty/README.md
+++ b/plugins/pagerduty/README.md
@@ -47,20 +47,13 @@ import {
## Client configuration
-The client takes a config object which contains an instance of the `discoveryApi` which is used to look up the `baseUrl` of the `proxy`, and a `eventUrl` which is defaulted to: "https://events.pagerduty.com/v2".
+If you want to override the default URL for events, you can add it to `app-config.yaml`.
-In `apis.ts`:
+In `app-config.yaml`:
-```ts
-createApiFactory({
- api: pagerDutyApiRef,
- deps: { discoveryApi: discoveryApiRef },
- factory: ({ discoveryApi }) =>
- new PagerDutyClient({
- discoveryApi: discoveryApi,
- eventUrl: "https://events.pagerduty.com/v2" //to override the default value
- }),
-}),
+```yaml
+pagerduty:
+ eventsBaseUrl: 'https://events.pagerduty.com/v2'
```
## Providing the API Token
diff --git a/plugins/pagerduty/src/api/client.ts b/plugins/pagerduty/src/api/client.ts
index 214e7d57a7..99e21ef6d3 100644
--- a/plugins/pagerduty/src/api/client.ts
+++ b/plugins/pagerduty/src/api/client.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { createApiRef } from '@backstage/core';
+import { createApiRef, DiscoveryApi, ConfigApi } from '@backstage/core';
import { Service, Incident, OnCall } from '../components/types';
import {
PagerDutyApi,
@@ -34,6 +34,15 @@ export const pagerDutyApiRef = createApiRef({
});
export class PagerDutyClient implements PagerDutyApi {
+ static fromConfig(configApi: ConfigApi, discoveryApi: DiscoveryApi) {
+ const eventsBaseUrl: string =
+ configApi.getOptionalString('pagerDuty.eventsBaseUrl') ??
+ 'https://events.pagerduty.com/v2';
+ return new PagerDutyClient({
+ eventsBaseUrl,
+ discoveryApi,
+ });
+ }
constructor(private readonly config: ClientApiConfig) {}
async getServiceByIntegrationKey(integrationKey: string): Promise {
@@ -98,7 +107,8 @@ export class PagerDutyClient implements PagerDutyApi {
};
return this.request(
- `${this.config.eventsUrl ?? 'https://events.pagerduty.com/v2'}/enqueue`,
+ `${this.config.eventsBaseUrl ??
+ 'https://events.pagerduty.com/v2'}/enqueue`,
options,
);
}
diff --git a/plugins/pagerduty/src/api/types.ts b/plugins/pagerduty/src/api/types.ts
index 4b3ae74cc1..733f171489 100644
--- a/plugins/pagerduty/src/api/types.ts
+++ b/plugins/pagerduty/src/api/types.ts
@@ -62,7 +62,7 @@ export type OnCallsResponse = {
};
export type ClientApiConfig = {
- eventsUrl?: string;
+ eventsBaseUrl?: string;
discoveryApi: DiscoveryApi;
};
diff --git a/plugins/pagerduty/src/components/PagerDutyCard.test.tsx b/plugins/pagerduty/src/components/PagerDutyCard.test.tsx
index 26d67a55e4..34ddb63f71 100644
--- a/plugins/pagerduty/src/components/PagerDutyCard.test.tsx
+++ b/plugins/pagerduty/src/components/PagerDutyCard.test.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import React from 'react';
-import { render, waitFor, fireEvent } from '@testing-library/react';
+import { render, waitFor, fireEvent, act } from '@testing-library/react';
import { PagerDutyCard } from './PagerDutyCard';
import { Entity } from '@backstage/catalog-model';
import { wrapInTestApp } from '@backstage/test-utils';
@@ -26,7 +26,6 @@ import {
} from '@backstage/core';
import { pagerDutyApiRef, UnauthorizedError, PagerDutyClient } from '../api';
import { Service } from './types';
-import { act } from 'react-dom/test-utils';
const mockPagerDutyApi: Partial = {
getServiceByIntegrationKey: async () => [],
diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx
index 3d2a48b419..7ef57d67b4 100644
--- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx
+++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import React from 'react';
-import { render, fireEvent } from '@testing-library/react';
+import { render, fireEvent, act } from '@testing-library/react';
import { wrapInTestApp } from '@backstage/test-utils';
import {
ApiRegistry,
@@ -26,7 +26,6 @@ import {
} from '@backstage/core';
import { pagerDutyApiRef } from '../../api';
import { Entity } from '@backstage/catalog-model';
-import { act } from 'react-dom/test-utils';
import { TriggerDialog } from './TriggerDialog';
describe('TriggerDialog', () => {
diff --git a/plugins/pagerduty/src/plugin.ts b/plugins/pagerduty/src/plugin.ts
index 4e8f429b05..34796f491e 100644
--- a/plugins/pagerduty/src/plugin.ts
+++ b/plugins/pagerduty/src/plugin.ts
@@ -18,6 +18,7 @@ import {
createPlugin,
createRouteRef,
discoveryApiRef,
+ configApiRef,
} from '@backstage/core';
import { pagerDutyApiRef, PagerDutyClient } from './api';
@@ -31,11 +32,9 @@ export const plugin = createPlugin({
apis: [
createApiFactory({
api: pagerDutyApiRef,
- deps: { discoveryApi: discoveryApiRef },
- factory: ({ discoveryApi }) =>
- new PagerDutyClient({
- discoveryApi: discoveryApi,
- }),
+ deps: { discoveryApi: discoveryApiRef, configApi: configApiRef },
+ factory: ({ configApi, discoveryApi }) =>
+ PagerDutyClient.fromConfig(configApi, discoveryApi),
}),
],
});
From fa25d9f448934593c32ee43c80fe41f9252ed1c0 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Tue, 1 Dec 2020 16:58:35 +0100
Subject: [PATCH 41/52] rename file
---
.../src/components/PagerDutyCard.tsx | 169 ++++++++++++++++++
1 file changed, 169 insertions(+)
create mode 100644 plugins/pagerduty/src/components/PagerDutyCard.tsx
diff --git a/plugins/pagerduty/src/components/PagerDutyCard.tsx b/plugins/pagerduty/src/components/PagerDutyCard.tsx
new file mode 100644
index 0000000000..ac309e8a95
--- /dev/null
+++ b/plugins/pagerduty/src/components/PagerDutyCard.tsx
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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, { useState } from 'react';
+import { useApi, Progress } from '@backstage/core';
+import { Entity } from '@backstage/catalog-model';
+import {
+ Button,
+ Card,
+ CardContent,
+ CardHeader,
+ Divider,
+ makeStyles,
+} from '@material-ui/core';
+import { Incidents } from './Incident';
+import { EscalationPolicy } from './Escalation';
+import { useAsync } from 'react-use';
+import { Alert } from '@material-ui/lab';
+import { pagerDutyApiRef, UnauthorizedError } from '../api';
+import { IconLinkVertical } from '@backstage/plugin-catalog';
+import { PagerDutyIcon } from './PagerDutyIcon';
+import AlarmAddIcon from '@material-ui/icons/AlarmAdd';
+import { TriggerDialog } from './TriggerDialog';
+import { MissingTokenError } from './MissingTokenError';
+
+const useStyles = makeStyles(theme => ({
+ links: {
+ margin: theme.spacing(2, 0),
+ display: 'grid',
+ gridAutoFlow: 'column',
+ gridAutoColumns: 'min-content',
+ gridGap: theme.spacing(3),
+ },
+ triggerAlarm: {
+ paddingTop: 0,
+ paddingBottom: 0,
+ fontSize: '0.7rem',
+ textTransform: 'uppercase',
+ fontWeight: 600,
+ letterSpacing: 1.2,
+ lineHeight: 1.5,
+ '&:hover, &:focus, &.focus': {
+ backgroundColor: 'transparent',
+ textDecoration: 'none',
+ },
+ },
+}));
+
+export const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key';
+
+export const isPluginApplicableToEntity = (entity: Entity) =>
+ Boolean(entity.metadata.annotations?.[PAGERDUTY_INTEGRATION_KEY]);
+
+type Props = {
+ entity: Entity;
+};
+
+export const PagerDutyCard = ({ entity }: Props) => {
+ const classes = useStyles();
+ const api = useApi(pagerDutyApiRef);
+ const [showDialog, setShowDialog] = useState(false);
+ const [refreshIncidents, setRefreshIncidents] = useState(false);
+ const integrationKey = entity.metadata.annotations![
+ PAGERDUTY_INTEGRATION_KEY
+ ];
+
+ const { value: service, loading, error } = useAsync(async () => {
+ const services = await api.getServiceByIntegrationKey(integrationKey);
+
+ return {
+ id: services[0].id,
+ name: services[0].name,
+ url: services[0].html_url,
+ policyId: services[0].escalation_policy.id,
+ };
+ });
+
+ const handleDialog = () => {
+ setShowDialog(!showDialog);
+ };
+
+ if (error instanceof UnauthorizedError) {
+ return ;
+ }
+
+ if (error) {
+ return (
+
+ Error encountered while fetching information. {error.message}
+
+ );
+ }
+
+ if (loading) {
+ return ;
+ }
+
+ const pagerdutyLink = {
+ title: 'View in PagerDuty',
+ href: service!.url,
+ };
+
+ const triggerAlarm = {
+ title: 'Trigger Alarm',
+ action: (
+
+ Trigger Alarm
+
+ ),
+ };
+
+ const onTriggerRefresh = () => {
+ setRefreshIncidents(true);
+ };
+
+ return (
+
+
+ }
+ />
+ }
+ action={triggerAlarm.action}
+ />
+
+ }
+ />
+
+
+
+
+
+
+
+ );
+};
From e7807c4c79522491ef3705aa26845aad3dc20444 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Tue, 1 Dec 2020 18:23:39 +0100
Subject: [PATCH 42/52] remove moment
---
plugins/pagerduty/package.json | 12 ++++++------
.../src/components/Incident/IncidentListItem.tsx | 6 +++---
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json
index 2fce2ebef3..64cc5a0893 100644
--- a/plugins/pagerduty/package.json
+++ b/plugins/pagerduty/package.json
@@ -20,21 +20,21 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/core": "^0.3.0",
- "@backstage/theme": "^0.2.1",
"@backstage/catalog-model": "^0.2.0",
- "@backstage/test-utils": "^0.1.2",
+ "@backstage/core": "^0.3.0",
"@backstage/plugin-catalog": "^0.2.1",
+ "@backstage/test-utils": "^0.1.2",
+ "@backstage/theme": "^0.2.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
+ "date-fns": "^2.16.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
- "react-use": "^15.3.3",
- "moment": "^2.27.0"
+ "react-use": "^15.3.3"
},
"devDependencies": {
- "@backstage/cli": "^0.2.0",
+ "@backstage/cli": "^0.3.1",
"@backstage/dev-utils": "^0.1.3",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
diff --git a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx
index 488631b834..7a34501325 100644
--- a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx
+++ b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx
@@ -27,7 +27,7 @@ import {
Typography,
} from '@material-ui/core';
import { StatusError, StatusWarning } from '@backstage/core';
-import moment from 'moment';
+import { formatDistanceToNowStrict } from 'date-fns';
import { Incident } from '../types';
import { PagerDutyIcon } from '../PagerDutyIcon';
@@ -54,7 +54,7 @@ type Props = {
export const IncidentListItem = ({ incident }: Props) => {
const classes = useStyles();
const user = incident.assignments[0]?.assignee;
- const createdAt = moment(incident.created_at).fromNow();
+ const createdAt = formatDistanceToNowStrict(new Date(incident.created_at));
return (
@@ -77,7 +77,7 @@ export const IncidentListItem = ({ incident }: Props) => {
}}
secondary={
- Created {createdAt} and assigned to{' '}
+ Created {createdAt} ago and assigned to{' '}
Date: Thu, 3 Dec 2020 14:25:58 +0100
Subject: [PATCH 43/52] revert changes
---
.../IconLinkVertical/IconLinkVertical.tsx | 26 ++-----------------
1 file changed, 2 insertions(+), 24 deletions(-)
diff --git a/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx b/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx
index 4a8bf2a0ab..dd267dde35 100644
--- a/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx
+++ b/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx
@@ -25,10 +25,9 @@ export type IconLinkVerticalProps = {
disabled?: boolean;
title?: string;
label: string;
- action?: React.ReactNode;
};
-const useIconStyles = makeStyles(theme => ({
+const useIconStyles = makeStyles({
link: {
display: 'grid',
justifyItems: 'center',
@@ -44,16 +43,12 @@ const useIconStyles = makeStyles(theme => ({
fontWeight: 600,
letterSpacing: 1.2,
},
- linkStyle: {
- color: theme.palette.secondary.main,
- },
-}));
+});
export function IconLinkVertical({
icon = ,
href = '#',
disabled = false,
- action,
...props
}: IconLinkVerticalProps) {
const classes = useIconStyles();
@@ -72,23 +67,6 @@ export function IconLinkVertical({
);
}
- if (action) {
- return (
-
- {icon}
- {action}
-
- );
- }
-
// Absolute links should not be using RouterLink
if (href?.startsWith('//') || href?.includes('://')) {
return (
From 3dee3093dee7ab0eb696997a9d35ff60c5b562ea Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Fri, 4 Dec 2020 09:28:48 +0100
Subject: [PATCH 44/52] fix updating incidents and create AboutCard and use it
---
.../src/components/About/AboutCard.tsx | 33 ++++++
.../src/components/About/SubHeader.tsx | 50 ++++++++
.../src/components/About/VerticalIcon.tsx | 85 ++++++++++++++
.../pagerduty/src/components/About/index.ts | 19 +++
.../src/components/Incident/Incidents.tsx | 6 +-
.../src/components/PagerDutyCard.test.tsx | 12 +-
.../src/components/PagerDutyCard.tsx | 110 +++++++-----------
.../TriggerDialog/TriggerDialog.test.tsx | 2 +-
.../TriggerDialog/TriggerDialog.tsx | 24 ++--
plugins/pagerduty/src/components/types.ts | 7 ++
10 files changed, 254 insertions(+), 94 deletions(-)
create mode 100644 plugins/pagerduty/src/components/About/AboutCard.tsx
create mode 100644 plugins/pagerduty/src/components/About/SubHeader.tsx
create mode 100644 plugins/pagerduty/src/components/About/VerticalIcon.tsx
create mode 100644 plugins/pagerduty/src/components/About/index.ts
diff --git a/plugins/pagerduty/src/components/About/AboutCard.tsx b/plugins/pagerduty/src/components/About/AboutCard.tsx
new file mode 100644
index 0000000000..7d5419908a
--- /dev/null
+++ b/plugins/pagerduty/src/components/About/AboutCard.tsx
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { Card, CardContent, CardHeader, Divider } from '@material-ui/core';
+import { SubHeader } from './SubHeader';
+import { SubHeaderLink } from '../types';
+
+type Props = {
+ title: string;
+ links: SubHeaderLink[];
+ content: React.ReactNode;
+};
+
+export const AboutCard = ({ title, links, content }: Props) => (
+
+ } />
+
+ {content}
+
+);
diff --git a/plugins/pagerduty/src/components/About/SubHeader.tsx b/plugins/pagerduty/src/components/About/SubHeader.tsx
new file mode 100644
index 0000000000..5e4ea96fe0
--- /dev/null
+++ b/plugins/pagerduty/src/components/About/SubHeader.tsx
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { VerticalIcon } from './VerticalIcon';
+import { SubHeaderLink } from '../types';
+import { makeStyles } from '@material-ui/core';
+
+const useStyles = makeStyles(theme => ({
+ links: {
+ margin: theme.spacing(2, 0),
+ display: 'grid',
+ gridAutoFlow: 'column',
+ gridAutoColumns: 'min-content',
+ gridGap: theme.spacing(3),
+ },
+}));
+
+type Props = {
+ links: SubHeaderLink[];
+};
+
+export const SubHeader = ({ links }: Props) => {
+ const classes = useStyles();
+ return (
+
+ {links.map(link => (
+
+ ))}
+
+ );
+};
diff --git a/plugins/pagerduty/src/components/About/VerticalIcon.tsx b/plugins/pagerduty/src/components/About/VerticalIcon.tsx
new file mode 100644
index 0000000000..5463372ac0
--- /dev/null
+++ b/plugins/pagerduty/src/components/About/VerticalIcon.tsx
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 classnames from 'classnames';
+import { makeStyles, Link } from '@material-ui/core';
+import LinkIcon from '@material-ui/icons/Link';
+import { Link as RouterLink } from 'react-router-dom';
+
+export type VerticalIconProps = {
+ icon?: React.ReactNode;
+ href?: string;
+ title?: string;
+ label: string;
+ action?: React.ReactNode;
+};
+
+const useIconStyles = makeStyles(theme => ({
+ link: {
+ display: 'grid',
+ justifyItems: 'center',
+ gridGap: 4,
+ textAlign: 'center',
+ },
+ label: {
+ fontSize: '0.7rem',
+ textTransform: 'uppercase',
+ fontWeight: 600,
+ letterSpacing: 1.2,
+ },
+ linkStyle: {
+ color: theme.palette.secondary.main,
+ },
+}));
+
+export function VerticalIcon({
+ icon = ,
+ href = '#',
+ action,
+ ...props
+}: VerticalIconProps) {
+ const classes = useIconStyles();
+
+ if (action) {
+ return (
+
+ {icon}
+ {action}
+
+ );
+ }
+
+ // Absolute links should not be using RouterLink
+ if (href?.startsWith('//') || href?.includes('://')) {
+ return (
+
+ {icon}
+ {props.label}
+
+ );
+ }
+
+ return (
+
+ {icon}
+ {props.label}
+
+ );
+}
diff --git a/plugins/pagerduty/src/components/About/index.ts b/plugins/pagerduty/src/components/About/index.ts
new file mode 100644
index 0000000000..4e495daa54
--- /dev/null
+++ b/plugins/pagerduty/src/components/About/index.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { AboutCard } from './AboutCard';
+// export { SubHeader } from './SubHeader';
+// export { VerticalIcon } from './VerticalIcon';
diff --git a/plugins/pagerduty/src/components/Incident/Incidents.tsx b/plugins/pagerduty/src/components/Incident/Incidents.tsx
index 4b229fb4bc..732a582cdb 100644
--- a/plugins/pagerduty/src/components/Incident/Incidents.tsx
+++ b/plugins/pagerduty/src/components/Incident/Incidents.tsx
@@ -25,16 +25,14 @@ import { Alert } from '@material-ui/lab';
type Props = {
serviceId: string;
- refreshIncidents: Boolean;
+ refreshIncidents: boolean;
};
export const Incidents = ({ serviceId, refreshIncidents }: Props) => {
const api = useApi(pagerDutyApiRef);
const [{ value: incidents, loading, error }, getIncidents] = useAsyncFn(
- async () => {
- return await api.getIncidentsByServiceId(serviceId);
- },
+ async () => await api.getIncidentsByServiceId(serviceId),
);
useEffect(() => {
diff --git a/plugins/pagerduty/src/components/PagerDutyCard.test.tsx b/plugins/pagerduty/src/components/PagerDutyCard.test.tsx
index 34ddb63f71..a6615aee82 100644
--- a/plugins/pagerduty/src/components/PagerDutyCard.test.tsx
+++ b/plugins/pagerduty/src/components/PagerDutyCard.test.tsx
@@ -85,11 +85,9 @@ describe('PageDutyCard', () => {
),
);
await waitFor(() => !queryByTestId('progress'));
- expect(getByText('View in PagerDuty')).toBeInTheDocument();
- expect(getByText('Trigger Alarm')).toBeInTheDocument();
- expect(
- getByText('Nice! No incidents are assigned to you!'),
- ).toBeInTheDocument();
+ expect(getByText('Service Directory')).toBeInTheDocument();
+ expect(getByText('Create Incident')).toBeInTheDocument();
+ expect(getByText('Nice! No incidents found!')).toBeInTheDocument();
expect(getByText('Empty escalation policy')).toBeInTheDocument();
});
@@ -141,8 +139,8 @@ describe('PageDutyCard', () => {
),
);
await waitFor(() => !queryByTestId('progress'));
- expect(getByText('View in PagerDuty')).toBeInTheDocument();
- expect(getByText('Trigger Alarm')).toBeInTheDocument();
+ expect(getByText('Service Directory')).toBeInTheDocument();
+ expect(getByText('Create Incident')).toBeInTheDocument();
const triggerButton = getByTestId('trigger-button');
await act(async () => {
fireEvent.click(triggerButton);
diff --git a/plugins/pagerduty/src/components/PagerDutyCard.tsx b/plugins/pagerduty/src/components/PagerDutyCard.tsx
index ac309e8a95..8fd20f37e8 100644
--- a/plugins/pagerduty/src/components/PagerDutyCard.tsx
+++ b/plugins/pagerduty/src/components/PagerDutyCard.tsx
@@ -13,36 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React, { useState } from 'react';
+import React, { useState, useCallback } from 'react';
import { useApi, Progress } from '@backstage/core';
import { Entity } from '@backstage/catalog-model';
-import {
- Button,
- Card,
- CardContent,
- CardHeader,
- Divider,
- makeStyles,
-} from '@material-ui/core';
+import { Button, makeStyles } from '@material-ui/core';
import { Incidents } from './Incident';
import { EscalationPolicy } from './Escalation';
import { useAsync } from 'react-use';
import { Alert } from '@material-ui/lab';
import { pagerDutyApiRef, UnauthorizedError } from '../api';
-import { IconLinkVertical } from '@backstage/plugin-catalog';
-import { PagerDutyIcon } from './PagerDutyIcon';
import AlarmAddIcon from '@material-ui/icons/AlarmAdd';
import { TriggerDialog } from './TriggerDialog';
-import { MissingTokenError } from './MissingTokenError';
+import { MissingTokenError } from './Errors/MissingTokenError';
+import WebIcon from '@material-ui/icons/Web';
+import { AboutCard } from './About/AboutCard';
-const useStyles = makeStyles(theme => ({
- links: {
- margin: theme.spacing(2, 0),
- display: 'grid',
- gridAutoFlow: 'column',
- gridAutoColumns: 'min-content',
- gridGap: theme.spacing(3),
- },
+const useStyles = makeStyles({
triggerAlarm: {
paddingTop: 0,
paddingBottom: 0,
@@ -56,7 +42,7 @@ const useStyles = makeStyles(theme => ({
textDecoration: 'none',
},
},
-}));
+});
export const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key';
@@ -76,6 +62,14 @@ export const PagerDutyCard = ({ entity }: Props) => {
PAGERDUTY_INTEGRATION_KEY
];
+ const handleRefresh = useCallback(() => {
+ setRefreshIncidents(x => !x);
+ }, []);
+
+ const handleDialog = useCallback(() => {
+ setShowDialog(x => !x);
+ }, []);
+
const { value: service, loading, error } = useAsync(async () => {
const services = await api.getServiceByIntegrationKey(integrationKey);
@@ -87,10 +81,6 @@ export const PagerDutyCard = ({ entity }: Props) => {
};
});
- const handleDialog = () => {
- setShowDialog(!showDialog);
- };
-
if (error instanceof UnauthorizedError) {
return ;
}
@@ -107,13 +97,14 @@ export const PagerDutyCard = ({ entity }: Props) => {
return ;
}
- const pagerdutyLink = {
- title: 'View in PagerDuty',
+ const serviceLink = {
+ title: 'Service Directory',
href: service!.url,
+ icon: ,
};
- const triggerAlarm = {
- title: 'Trigger Alarm',
+ const triggerLink = {
+ title: 'Create Incident',
action: (
{
onClick={handleDialog}
className={classes.triggerAlarm}
>
- Trigger Alarm
+ Create Incident
),
- };
-
- const onTriggerRefresh = () => {
- setRefreshIncidents(true);
+ icon: ,
};
return (
-
-
- }
- />
- }
- action={triggerAlarm.action}
- />
-
- }
- />
-
-
-
-
-
-
-
+
+
+
+
+ >
+ }
+ />
);
};
diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx
index 7ef57d67b4..8073ab44b5 100644
--- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx
+++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx
@@ -70,7 +70,7 @@ describe('TriggerDialog', () => {
handleDialog={() => {}}
name={entity.metadata.name}
integrationKey="abc123"
- onTriggerRefresh={() => {}}
+ onIncidentCreated={() => {}}
/>
,
),
diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx
index 4717b8fece..4fa88a1b9a 100644
--- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx
+++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx
@@ -35,7 +35,7 @@ type Props = {
integrationKey: string;
showDialog: boolean;
handleDialog: () => void;
- onTriggerRefresh: () => void;
+ onIncidentCreated: () => void;
};
export const TriggerDialog = ({
@@ -43,7 +43,7 @@ export const TriggerDialog = ({
integrationKey,
showDialog,
handleDialog,
- onTriggerRefresh,
+ onIncidentCreated: onIncidentCreated,
}: Props) => {
const alertApi = useApi(alertApiRef);
const identityApi = useApi(identityApiRef);
@@ -72,24 +72,20 @@ export const TriggerDialog = ({
alertApi.post({
message: `Alarm successfully triggered by ${userName}`,
});
- onTriggerRefresh();
+ onIncidentCreated();
handleDialog();
}
+ }, [value, alertApi, handleDialog, userName, onIncidentCreated]);
- if (error) {
- alertApi.post({
- message: `Failed to trigger alarm. ${error.message}`,
- severity: 'error',
- });
- }
- }, [value, error, alertApi, handleDialog, userName, onTriggerRefresh]);
-
- if (!showDialog) {
- return null;
+ if (error) {
+ alertApi.post({
+ message: `Failed to trigger alarm. ${error.message}`,
+ severity: 'error',
+ });
}
return (
-
+
This action will trigger an incident for "{name}" .
diff --git a/plugins/pagerduty/src/components/types.ts b/plugins/pagerduty/src/components/types.ts
index a93791fe8e..371f4eea6d 100644
--- a/plugins/pagerduty/src/components/types.ts
+++ b/plugins/pagerduty/src/components/types.ts
@@ -56,3 +56,10 @@ export type User = {
html_url: string;
name: string;
};
+
+export type SubHeaderLink = {
+ title: string;
+ href?: string;
+ icon: React.ReactNode;
+ action?: React.ReactNode;
+};
From 18c2dcb368f4dfcefdb0fcd211dbf5eb1c772c32 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Fri, 4 Dec 2020 09:58:45 +0100
Subject: [PATCH 45/52] remove pagerduty logo
---
.../components/Escalation/EscalationUser.tsx | 4 +--
.../Incident/IncidentEmptyState.tsx | 4 +--
.../components/Incident/IncidentListItem.tsx | 4 +--
.../components/Incident/Incidents.test.tsx | 4 +--
.../src/components/PagerDutyIcon.tsx | 27 -------------------
5 files changed, 7 insertions(+), 36 deletions(-)
delete mode 100644 plugins/pagerduty/src/components/PagerDutyIcon.tsx
diff --git a/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx b/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx
index 3847896e89..41995c86f5 100644
--- a/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx
+++ b/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx
@@ -27,7 +27,7 @@ import {
} from '@material-ui/core';
import Avatar from '@material-ui/core/Avatar';
import EmailIcon from '@material-ui/icons/Email';
-import { PagerDutyIcon } from '../PagerDutyIcon';
+import OpenInBrowserIcon from '@material-ui/icons/OpenInBrowser';
import { User } from '../types';
const useStyles = makeStyles({
@@ -69,7 +69,7 @@ export const EscalationUser = ({ user }: Props) => {
rel="noopener noreferrer"
color="primary"
>
-
+
diff --git a/plugins/pagerduty/src/components/Incident/IncidentEmptyState.tsx b/plugins/pagerduty/src/components/Incident/IncidentEmptyState.tsx
index ded161261e..c140620bb3 100644
--- a/plugins/pagerduty/src/components/Incident/IncidentEmptyState.tsx
+++ b/plugins/pagerduty/src/components/Incident/IncidentEmptyState.tsx
@@ -15,14 +15,14 @@
*/
import React from 'react';
-import { Grid } from '@material-ui/core';
+import { Grid, Typography } from '@material-ui/core';
import EmptyStateImage from '../../assets/emptyState.svg';
export const IncidentsEmptyState = () => {
return (
- Nice! No incidents are assigned to you!
+ Nice! No incidents found!
{
rel="noopener noreferrer"
color="primary"
>
-
+
diff --git a/plugins/pagerduty/src/components/Incident/Incidents.test.tsx b/plugins/pagerduty/src/components/Incident/Incidents.test.tsx
index 3c5e30e0cc..9da9f9f11b 100644
--- a/plugins/pagerduty/src/components/Incident/Incidents.test.tsx
+++ b/plugins/pagerduty/src/components/Incident/Incidents.test.tsx
@@ -40,9 +40,7 @@ describe('Incidents', () => {
),
);
await waitFor(() => !queryByTestId('progress'));
- expect(
- getByText('Nice! No incidents are assigned to you!'),
- ).toBeInTheDocument();
+ expect(getByText('Nice! No incidents found!')).toBeInTheDocument();
});
it('Renders all incidents', async () => {
diff --git a/plugins/pagerduty/src/components/PagerDutyIcon.tsx b/plugins/pagerduty/src/components/PagerDutyIcon.tsx
deleted file mode 100644
index 67cfc0036a..0000000000
--- a/plugins/pagerduty/src/components/PagerDutyIcon.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2020 Spotify AB
- *
- * 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 SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon';
-
-export const PagerDutyIcon = (props: SvgIconProps) =>
- React.createElement(
- SvgIcon,
- props,
-
-
- ,
- );
From 6f23e968303fb6a5ac91bd3021527d3cfcfa79d5 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Fri, 4 Dec 2020 10:03:26 +0100
Subject: [PATCH 46/52] refactor structure
---
.../{ => Errors}/MissingTokenError.tsx | 0
.../pagerduty/src/components/Errors/index.ts | 17 +++++++++++++++++
2 files changed, 17 insertions(+)
rename plugins/pagerduty/src/components/{ => Errors}/MissingTokenError.tsx (100%)
create mode 100644 plugins/pagerduty/src/components/Errors/index.ts
diff --git a/plugins/pagerduty/src/components/MissingTokenError.tsx b/plugins/pagerduty/src/components/Errors/MissingTokenError.tsx
similarity index 100%
rename from plugins/pagerduty/src/components/MissingTokenError.tsx
rename to plugins/pagerduty/src/components/Errors/MissingTokenError.tsx
diff --git a/plugins/pagerduty/src/components/Errors/index.ts b/plugins/pagerduty/src/components/Errors/index.ts
new file mode 100644
index 0000000000..3c2dfa65f2
--- /dev/null
+++ b/plugins/pagerduty/src/components/Errors/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { MissingTokenError } from './MissingTokenError';
From 1c6f8417d111e8a69cfba997f74ff6128f4d7a8f Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Fri, 4 Dec 2020 10:14:05 +0100
Subject: [PATCH 47/52] fix getIncident query
---
plugins/pagerduty/src/api/client.ts | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/plugins/pagerduty/src/api/client.ts b/plugins/pagerduty/src/api/client.ts
index 99e21ef6d3..527a56b2af 100644
--- a/plugins/pagerduty/src/api/client.ts
+++ b/plugins/pagerduty/src/api/client.ts
@@ -56,7 +56,7 @@ export class PagerDutyClient implements PagerDutyApi {
}
async getIncidentsByServiceId(serviceId: string): Promise {
- const params = `service_ids[]=${serviceId}`;
+ const params = `statuses[]=triggered&statuses[]=acknowledged&service_ids[]=${serviceId}`;
const url = `${await this.config.discoveryApi.getBaseUrl(
'proxy',
)}/pagerduty/incidents?${params}`;
@@ -106,11 +106,9 @@ export class PagerDutyClient implements PagerDutyApi {
body,
};
- return this.request(
- `${this.config.eventsBaseUrl ??
- 'https://events.pagerduty.com/v2'}/enqueue`,
- options,
- );
+ const url = this.config.eventsBaseUrl ?? 'https://events.pagerduty.com/v2';
+
+ return this.request(`${url}/enqueue`, options);
}
private async getByUrl(url: string): Promise {
From afb65b2f886dbce5112a862cc27b0bdbcf1e47a1 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Fri, 4 Dec 2020 10:14:50 +0100
Subject: [PATCH 48/52] remove plugin-catalog dep
---
plugins/pagerduty/package.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json
index 64cc5a0893..a7b5c3b58f 100644
--- a/plugins/pagerduty/package.json
+++ b/plugins/pagerduty/package.json
@@ -22,15 +22,16 @@
"dependencies": {
"@backstage/catalog-model": "^0.2.0",
"@backstage/core": "^0.3.0",
- "@backstage/plugin-catalog": "^0.2.1",
"@backstage/test-utils": "^0.1.2",
"@backstage/theme": "^0.2.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
+ "classnames": "^2.2.6",
"date-fns": "^2.16.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
+ "react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3"
},
"devDependencies": {
From 02be8ca4f1a7095ea88a95642a4c4ee256c8b6b6 Mon Sep 17 00:00:00 2001
From: samiramkr
Date: Fri, 4 Dec 2020 11:41:30 +0100
Subject: [PATCH 49/52] Delete PagerdutyCard.tsx
---
.../src/components/PagerdutyCard.tsx | 169 ------------------
1 file changed, 169 deletions(-)
delete mode 100644 plugins/pagerduty/src/components/PagerdutyCard.tsx
diff --git a/plugins/pagerduty/src/components/PagerdutyCard.tsx b/plugins/pagerduty/src/components/PagerdutyCard.tsx
deleted file mode 100644
index ac309e8a95..0000000000
--- a/plugins/pagerduty/src/components/PagerdutyCard.tsx
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright 2020 Spotify AB
- *
- * 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, { useState } from 'react';
-import { useApi, Progress } from '@backstage/core';
-import { Entity } from '@backstage/catalog-model';
-import {
- Button,
- Card,
- CardContent,
- CardHeader,
- Divider,
- makeStyles,
-} from '@material-ui/core';
-import { Incidents } from './Incident';
-import { EscalationPolicy } from './Escalation';
-import { useAsync } from 'react-use';
-import { Alert } from '@material-ui/lab';
-import { pagerDutyApiRef, UnauthorizedError } from '../api';
-import { IconLinkVertical } from '@backstage/plugin-catalog';
-import { PagerDutyIcon } from './PagerDutyIcon';
-import AlarmAddIcon from '@material-ui/icons/AlarmAdd';
-import { TriggerDialog } from './TriggerDialog';
-import { MissingTokenError } from './MissingTokenError';
-
-const useStyles = makeStyles(theme => ({
- links: {
- margin: theme.spacing(2, 0),
- display: 'grid',
- gridAutoFlow: 'column',
- gridAutoColumns: 'min-content',
- gridGap: theme.spacing(3),
- },
- triggerAlarm: {
- paddingTop: 0,
- paddingBottom: 0,
- fontSize: '0.7rem',
- textTransform: 'uppercase',
- fontWeight: 600,
- letterSpacing: 1.2,
- lineHeight: 1.5,
- '&:hover, &:focus, &.focus': {
- backgroundColor: 'transparent',
- textDecoration: 'none',
- },
- },
-}));
-
-export const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key';
-
-export const isPluginApplicableToEntity = (entity: Entity) =>
- Boolean(entity.metadata.annotations?.[PAGERDUTY_INTEGRATION_KEY]);
-
-type Props = {
- entity: Entity;
-};
-
-export const PagerDutyCard = ({ entity }: Props) => {
- const classes = useStyles();
- const api = useApi(pagerDutyApiRef);
- const [showDialog, setShowDialog] = useState(false);
- const [refreshIncidents, setRefreshIncidents] = useState(false);
- const integrationKey = entity.metadata.annotations![
- PAGERDUTY_INTEGRATION_KEY
- ];
-
- const { value: service, loading, error } = useAsync(async () => {
- const services = await api.getServiceByIntegrationKey(integrationKey);
-
- return {
- id: services[0].id,
- name: services[0].name,
- url: services[0].html_url,
- policyId: services[0].escalation_policy.id,
- };
- });
-
- const handleDialog = () => {
- setShowDialog(!showDialog);
- };
-
- if (error instanceof UnauthorizedError) {
- return ;
- }
-
- if (error) {
- return (
-
- Error encountered while fetching information. {error.message}
-
- );
- }
-
- if (loading) {
- return ;
- }
-
- const pagerdutyLink = {
- title: 'View in PagerDuty',
- href: service!.url,
- };
-
- const triggerAlarm = {
- title: 'Trigger Alarm',
- action: (
-
- Trigger Alarm
-
- ),
- };
-
- const onTriggerRefresh = () => {
- setRefreshIncidents(true);
- };
-
- return (
-
-
- }
- />
- }
- action={triggerAlarm.action}
- />
-
- }
- />
-
-
-
-
-
-
-
- );
-};
From fa9d634350425d44287f604729a4786f1ccf7a2f Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Fri, 4 Dec 2020 13:42:20 +0100
Subject: [PATCH 50/52] update dependencies version
---
plugins/pagerduty/package.json | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json
index a7b5c3b58f..4d9ec49d02 100644
--- a/plugins/pagerduty/package.json
+++ b/plugins/pagerduty/package.json
@@ -20,23 +20,23 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/catalog-model": "^0.2.0",
- "@backstage/core": "^0.3.0",
- "@backstage/test-utils": "^0.1.2",
+ "@backstage/catalog-model": "^0.4.0",
+ "@backstage/core": "^0.3.2",
+ "@backstage/test-utils": "^0.1.4",
"@backstage/theme": "^0.2.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"classnames": "^2.2.6",
- "date-fns": "^2.16.1",
+ "date-fns": "^2.15.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3"
},
"devDependencies": {
- "@backstage/cli": "^0.3.1",
- "@backstage/dev-utils": "^0.1.3",
+ "@backstage/cli": "^0.4.0",
+ "@backstage/dev-utils": "^0.1.5",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
From b7a5d90c83f3266d64e00ab3dac34933be4d7617 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Fri, 4 Dec 2020 14:56:53 +0100
Subject: [PATCH 51/52] fix import
---
.../pagerduty/src/components/Incident/IncidentEmptyState.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/plugins/pagerduty/src/components/Incident/IncidentEmptyState.tsx b/plugins/pagerduty/src/components/Incident/IncidentEmptyState.tsx
index c140620bb3..b697422b0f 100644
--- a/plugins/pagerduty/src/components/Incident/IncidentEmptyState.tsx
+++ b/plugins/pagerduty/src/components/Incident/IncidentEmptyState.tsx
@@ -16,7 +16,7 @@
import React from 'react';
import { Grid, Typography } from '@material-ui/core';
-import EmptyStateImage from '../../assets/emptyState.svg';
+import EmptyStateImage from '../../assets/emptystate.svg';
export const IncidentsEmptyState = () => {
return (
From 7c856e33174b1c675e619d229e60817b4c94c320 Mon Sep 17 00:00:00 2001
From: Samira Mokaram
Date: Fri, 4 Dec 2020 16:44:01 +0100
Subject: [PATCH 52/52] update dep version
---
plugins/pagerduty/package.json | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json
index 4d9ec49d02..4d58a1da42 100644
--- a/plugins/pagerduty/package.json
+++ b/plugins/pagerduty/package.json
@@ -22,7 +22,6 @@
"dependencies": {
"@backstage/catalog-model": "^0.4.0",
"@backstage/core": "^0.3.2",
- "@backstage/test-utils": "^0.1.4",
"@backstage/theme": "^0.2.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
@@ -37,13 +36,15 @@
"devDependencies": {
"@backstage/cli": "^0.4.0",
"@backstage/dev-utils": "^0.1.5",
+ "@backstage/test-utils": "^0.1.4",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
"@types/jest": "^26.0.7",
"@types/node": "^12.0.0",
- "msw": "^0.20.5",
- "node-fetch": "^2.6.1"
+ "msw": "^0.21.2",
+ "node-fetch": "^2.6.1",
+ "cross-fetch": "^3.0.6"
},
"files": [
"dist"