Skip to content

Commit b3a33e1

Browse files
Pagerduty plugin instructions and configuration flow (#41)
Co-authored-by: Cafer Elgin <[email protected]>
1 parent ddab1a6 commit b3a33e1

File tree

9 files changed

+726
-51
lines changed

9 files changed

+726
-51
lines changed

plugins/pagerduty-incidents/src/components/App.test.tsx

Lines changed: 164 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import { render, waitFor } from "@testing-library/react";
2-
1+
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
32
import fetchMock from "jest-fetch-mock";
4-
53
import App from "./App";
4+
import { act } from "react-dom/test-utils";
5+
66
describe("App", () => {
77
beforeEach(() => {
88
// Reset fetchMock before each test to start with a clean slate
99
fetchMock.resetMocks();
1010
});
1111

12-
it("gets no pd mapping", async () => {
12+
it("does initial configuration when not configured", async () => {
1313
const mockBodies = {
1414
"https://api.getcortexapp.com/catalog/inventory-planner/openapi": {
1515
info: {
@@ -25,13 +25,27 @@ describe("App", () => {
2525
},
2626
],
2727
},
28-
"https://api.pagerduty.com/services": {
29-
services: [],
28+
"https://api.getcortexapp.com/plugins/pagerduty-incidents": {
29+
description:
30+
"https://plugin-marketplace.s3.us-east-2.amazonaws.com/pagerduty-plugin/ui.html",
3031
},
32+
"https://api.getcortexapp.com/api/internal/v1/secrets": {},
33+
"https://api.getcortexapp.com/api/internal/v1/proxies": {},
34+
"https://plugin-marketplace.s3.us-east-2.amazonaws.com/pagerduty-plugin/ui.html":
35+
{},
3136
};
3237

3338
fetchMock.mockResponse(async (req) => {
3439
const url = req.url.split("?")[0];
40+
if (url === "https://api.pagerduty.com/abilities") {
41+
// return success if authorization header contains our fake token
42+
if (req.headers.get("Authorization") === "Token token=fake_token_123") {
43+
return {
44+
status: 200,
45+
body: JSON.stringify({ abilities: [] }),
46+
};
47+
}
48+
}
3549
if (!mockBodies[url]) {
3650
return {
3751
status: 404,
@@ -44,13 +58,155 @@ describe("App", () => {
4458
};
4559
});
4660

47-
const { getByText } = render(<App />);
61+
const { getByText, getByLabelText, getByRole } = render(<App />);
4862

63+
// Wait for initial text to load
4964
await waitFor(() => {
5065
const element = getByText(
51-
/This entity is not associated with any PagerDuty service./
66+
/To configure this plugin automatically, you need a PagerDuty API key/i
5267
);
5368
expect(element).toBeInTheDocument();
69+
});
70+
71+
// Simulate clicking the "Configure" button to open the modal
72+
const configureButton = screen.getByRole("button", { name: /Configure/i });
73+
fireEvent.click(configureButton);
74+
75+
// Simulate typing a fake token into the input field
76+
const tokenInput = getByLabelText(/PagerDuty REST API Token/i);
77+
fireEvent.change(tokenInput, { target: { value: "fake_token_123" } });
78+
79+
const submitButton = getByRole("button", { name: "Submit" });
80+
// Wait for the Submit button to be enabled
81+
await waitFor(
82+
() => {
83+
expect(submitButton).not.toBeDisabled();
84+
expect(submitButton).toBeVisible();
85+
},
86+
{ timeout: 1000 }
87+
);
88+
89+
await act(async () => {
90+
// Simulate clicking the Submit button
91+
fireEvent.click(submitButton);
92+
});
93+
94+
await waitFor(
95+
() => {
96+
const element = getByText(/Configuration completed successfully/i);
97+
expect(element).toBeInTheDocument();
98+
},
99+
{ timeout: 1000 }
100+
);
101+
102+
expect(fetch).toHaveBeenCalledWith("https://api.pagerduty.com/abilities", {
103+
headers: {
104+
Authorization: "Token token=fake_token_123",
105+
},
106+
});
107+
});
108+
109+
it("gets no PD mapping", async () => {
110+
const mockBodies = {
111+
"https://api.getcortexapp.com/catalog/inventory-planner/openapi": {
112+
info: {
113+
title: "Inventory Planner",
114+
description: "it is a inventory planner",
115+
"x-cortex-tag": "inventory-planner",
116+
"x-cortex-type": "service",
117+
},
118+
openapi: "3.0.1",
119+
servers: [
120+
{
121+
url: "/",
122+
},
123+
],
124+
},
125+
"https://api.pagerduty.com/abilities": {
126+
abilities: [],
127+
},
128+
"https://api.pagerduty.com/services/PXXXXXX": {
129+
service: {
130+
id: "PXXXXXX",
131+
type: "service",
132+
summary: "My Application Service",
133+
self: "https://api.pagerduty.com/services/PXXXXXX",
134+
html_url: "https://subdomain.pagerduty.com/service-directory/PXXXXXX",
135+
name: "My Application Service",
136+
auto_resolve_timeout: 14400,
137+
acknowledgement_timeout: 600,
138+
created_at: "2015-11-06T11:12:51-05:00",
139+
status: "active",
140+
alert_creation: "create_alerts_and_incidents",
141+
integrations: [],
142+
escalation_policy: {
143+
id: "PYYYYYY",
144+
type: "escalation_policy_reference",
145+
summary: "Another Escalation Policy",
146+
self: "https://api.pagerduty.com/escalation_policies/PYYYYYY",
147+
html_url:
148+
"https://subdomain.pagerduty.com/escalation_policies/PYYYYYY",
149+
},
150+
teams: [],
151+
},
152+
},
153+
"https://api.pagerduty.com/oncalls": {
154+
oncalls: [],
155+
},
156+
"https://api.pagerduty.com/services": {
157+
services: [
158+
{
159+
id: "PXXXXXX",
160+
type: "service",
161+
summary: "My Application Service",
162+
self: "https://api.pagerduty.com/services/PXXXXXX",
163+
html_url:
164+
"https://subdomain.pagerduty.com/service-directory/PXXXXXX",
165+
name: "My Application Service",
166+
auto_resolve_timeout: 14400,
167+
acknowledgement_timeout: 600,
168+
created_at: "2015-11-06T11:12:51-05:00",
169+
status: "active",
170+
alert_creation: "create_alerts_and_incidents",
171+
integrations: [],
172+
escalation_policy: {
173+
id: "PYYYYYY",
174+
type: "escalation_policy_reference",
175+
summary: "Another Escalation Policy",
176+
self: "https://api.pagerduty.com/escalation_policies/PYYYYYY",
177+
html_url:
178+
"https://subdomain.pagerduty.com/escalation_policies/PYYYYYY",
179+
},
180+
teams: [],
181+
},
182+
],
183+
},
184+
};
185+
186+
fetchMock.mockResponse(async (req) => {
187+
const url = req.url.split("?")[0];
188+
if (!mockBodies[url]) {
189+
return {
190+
status: 404,
191+
};
192+
}
193+
const body = mockBodies[url];
194+
return {
195+
status: 200,
196+
body: JSON.stringify(body),
197+
};
198+
});
199+
200+
const { getByText } = render(<App />);
201+
202+
await waitFor(() => {
203+
const element = getByText(/Select a service/);
204+
expect(element).toBeInTheDocument();
205+
expect(fetch).toHaveBeenCalledWith(
206+
expect.stringMatching(
207+
/https:\/\/api\.getcortexapp\.com\/catalog\/inventory-planner\/gitops-logs/
208+
)
209+
);
54210
expect(fetch).toHaveBeenCalledWith(
55211
expect.stringMatching(
56212
/https:\/\/api\.getcortexapp\.com\/catalog\/inventory-planner\/openapi/
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import type React from "react";
2+
3+
import {
4+
Text,
5+
Box,
6+
Button,
7+
Link,
8+
useDisclosure,
9+
Heading,
10+
} from "@chakra-ui/react";
11+
12+
import InstructionsModal from "./InstructionsModal";
13+
import { useState, useEffect } from "react";
14+
15+
import { usePluginContext } from "@cortexapps/plugin-core/components";
16+
17+
const Instructions: React.FC = () => {
18+
const { apiBaseUrl } = usePluginContext();
19+
const {
20+
isOpen: isInstuctionsModalOpen,
21+
onOpen: onInstructionsModalOpen,
22+
onClose: onInstructionsModalClose,
23+
} = useDisclosure();
24+
25+
const [isMarketplacePlugin, setIsMarketplacePlugin] = useState<
26+
boolean | null
27+
>(null);
28+
const [configCompleted, setConfigCompleted] = useState(false);
29+
30+
useEffect(() => {
31+
const fetchMarketplacePlugin = async (): Promise<void> => {
32+
try {
33+
const response = await fetch(
34+
`${apiBaseUrl}/plugins/pagerduty-incidents`
35+
);
36+
const { description } = await response.json();
37+
setIsMarketplacePlugin(
38+
description.includes(
39+
"https://plugin-marketplace.s3.us-east-2.amazonaws.com/pagerduty-plugin/ui.html"
40+
)
41+
);
42+
} catch (e) {
43+
setIsMarketplacePlugin(false);
44+
}
45+
};
46+
void fetchMarketplacePlugin();
47+
}, [apiBaseUrl]);
48+
49+
const onConfigCompleted = (): void => {
50+
setConfigCompleted(true);
51+
};
52+
53+
if (configCompleted) {
54+
return (
55+
<Box
56+
minH="400px"
57+
backgroundColor="light"
58+
margin={2}
59+
padding={4}
60+
borderRadius={2}
61+
>
62+
<Heading as="h2" size="md" marginBottom={4}>
63+
Configure PagerDuty Incidents Plugin
64+
</Heading>
65+
<Text>
66+
Configuration completed successfully. Please refresh the page to start
67+
using the plugin.
68+
</Text>
69+
</Box>
70+
);
71+
}
72+
73+
return (
74+
<Box
75+
minH="400px"
76+
backgroundColor="light"
77+
margin={2}
78+
padding={4}
79+
borderRadius={2}
80+
>
81+
<Heading as="h2" size="md" marginBottom={4}>
82+
Configure PagerDuty Incidents Plugin
83+
</Heading>
84+
{isMarketplacePlugin && (
85+
<>
86+
<Text>
87+
To configure this plugin automatically, you need a PagerDuty API key
88+
and permissions to create proxies and secrets in Cortex. Click the
89+
button below to enter your PagerDuty API key and do automatic
90+
configuration.
91+
</Text>
92+
<Button onClick={onInstructionsModalOpen} colorScheme="purple">
93+
Configure
94+
</Button>
95+
<InstructionsModal
96+
isOpen={isInstuctionsModalOpen}
97+
onClose={onInstructionsModalClose}
98+
onConfigCompleted={onConfigCompleted}
99+
/>
100+
</>
101+
)}
102+
{isMarketplacePlugin === false && (
103+
<Text>
104+
This plugin was not installed by the Plugin Marketplace, so it cannot
105+
be configured automatically. To configure it manually, follow the
106+
instructions in the plugin documentation{" "}
107+
<Link
108+
display="inline"
109+
target="_blank"
110+
href="https://github.com/cortexapps/cortex-plugins/blob/master/plugins/pagerduty-incidents/README.md"
111+
>
112+
here
113+
</Link>
114+
.
115+
</Text>
116+
)}
117+
</Box>
118+
);
119+
};
120+
121+
export default Instructions;

0 commit comments

Comments
 (0)