Skip to content

Pagerduty plugin instructions and configuration flow #41

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 164 additions & 8 deletions plugins/pagerduty-incidents/src/components/App.test.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { render, waitFor } from "@testing-library/react";

import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import fetchMock from "jest-fetch-mock";

import App from "./App";
import { act } from "react-dom/test-utils";

describe("App", () => {
beforeEach(() => {
// Reset fetchMock before each test to start with a clean slate
fetchMock.resetMocks();
});

it("gets no pd mapping", async () => {
it("does initial configuration when not configured", async () => {
const mockBodies = {
"https://api.getcortexapp.com/catalog/inventory-planner/openapi": {
info: {
Expand All @@ -25,13 +25,27 @@ describe("App", () => {
},
],
},
"https://api.pagerduty.com/services": {
services: [],
"https://api.getcortexapp.com/plugins/pagerduty-incidents": {
description:
"https://plugin-marketplace.s3.us-east-2.amazonaws.com/pagerduty-plugin/ui.html",
},
"https://api.getcortexapp.com/api/internal/v1/secrets": {},
"https://api.getcortexapp.com/api/internal/v1/proxies": {},
"https://plugin-marketplace.s3.us-east-2.amazonaws.com/pagerduty-plugin/ui.html":
{},
};

fetchMock.mockResponse(async (req) => {
const url = req.url.split("?")[0];
if (url === "https://api.pagerduty.com/abilities") {
// return success if authorization header contains our fake token
if (req.headers.get("Authorization") === "Token token=fake_token_123") {
return {
status: 200,
body: JSON.stringify({ abilities: [] }),
};
}
}
if (!mockBodies[url]) {
return {
status: 404,
Expand All @@ -44,13 +58,155 @@ describe("App", () => {
};
});

const { getByText } = render(<App />);
const { getByText, getByLabelText, getByRole } = render(<App />);

// Wait for initial text to load
await waitFor(() => {
const element = getByText(
/This entity is not associated with any PagerDuty service./
/To configure this plugin automatically, you need a PagerDuty API key/i
);
expect(element).toBeInTheDocument();
});

// Simulate clicking the "Configure" button to open the modal
const configureButton = screen.getByRole("button", { name: /Configure/i });
fireEvent.click(configureButton);

// Simulate typing a fake token into the input field
const tokenInput = getByLabelText(/PagerDuty REST API Token/i);
fireEvent.change(tokenInput, { target: { value: "fake_token_123" } });

const submitButton = getByRole("button", { name: "Submit" });
// Wait for the Submit button to be enabled
await waitFor(
() => {
expect(submitButton).not.toBeDisabled();
expect(submitButton).toBeVisible();
},
{ timeout: 1000 }
);

await act(async () => {
// Simulate clicking the Submit button
fireEvent.click(submitButton);
});

await waitFor(
() => {
const element = getByText(/Configuration completed successfully/i);
expect(element).toBeInTheDocument();
},
{ timeout: 1000 }
);

expect(fetch).toHaveBeenCalledWith("https://api.pagerduty.com/abilities", {
headers: {
Authorization: "Token token=fake_token_123",
},
});
});

it("gets no PD mapping", async () => {
const mockBodies = {
"https://api.getcortexapp.com/catalog/inventory-planner/openapi": {
info: {
title: "Inventory Planner",
description: "it is a inventory planner",
"x-cortex-tag": "inventory-planner",
"x-cortex-type": "service",
},
openapi: "3.0.1",
servers: [
{
url: "/",
},
],
},
"https://api.pagerduty.com/abilities": {
abilities: [],
},
"https://api.pagerduty.com/services/PXXXXXX": {
service: {
id: "PXXXXXX",
type: "service",
summary: "My Application Service",
self: "https://api.pagerduty.com/services/PXXXXXX",
html_url: "https://subdomain.pagerduty.com/service-directory/PXXXXXX",
name: "My Application Service",
auto_resolve_timeout: 14400,
acknowledgement_timeout: 600,
created_at: "2015-11-06T11:12:51-05:00",
status: "active",
alert_creation: "create_alerts_and_incidents",
integrations: [],
escalation_policy: {
id: "PYYYYYY",
type: "escalation_policy_reference",
summary: "Another Escalation Policy",
self: "https://api.pagerduty.com/escalation_policies/PYYYYYY",
html_url:
"https://subdomain.pagerduty.com/escalation_policies/PYYYYYY",
},
teams: [],
},
},
"https://api.pagerduty.com/oncalls": {
oncalls: [],
},
"https://api.pagerduty.com/services": {
services: [
{
id: "PXXXXXX",
type: "service",
summary: "My Application Service",
self: "https://api.pagerduty.com/services/PXXXXXX",
html_url:
"https://subdomain.pagerduty.com/service-directory/PXXXXXX",
name: "My Application Service",
auto_resolve_timeout: 14400,
acknowledgement_timeout: 600,
created_at: "2015-11-06T11:12:51-05:00",
status: "active",
alert_creation: "create_alerts_and_incidents",
integrations: [],
escalation_policy: {
id: "PYYYYYY",
type: "escalation_policy_reference",
summary: "Another Escalation Policy",
self: "https://api.pagerduty.com/escalation_policies/PYYYYYY",
html_url:
"https://subdomain.pagerduty.com/escalation_policies/PYYYYYY",
},
teams: [],
},
],
},
};

fetchMock.mockResponse(async (req) => {
const url = req.url.split("?")[0];
if (!mockBodies[url]) {
return {
status: 404,
};
}
const body = mockBodies[url];
return {
status: 200,
body: JSON.stringify(body),
};
});

const { getByText } = render(<App />);

await waitFor(() => {
const element = getByText(/Select a service/);
expect(element).toBeInTheDocument();
expect(fetch).toHaveBeenCalledWith(
expect.stringMatching(
/https:\/\/api\.getcortexapp\.com\/catalog\/inventory-planner\/gitops-logs/
)
);
expect(fetch).toHaveBeenCalledWith(
expect.stringMatching(
/https:\/\/api\.getcortexapp\.com\/catalog\/inventory-planner\/openapi/
Expand Down
121 changes: 121 additions & 0 deletions plugins/pagerduty-incidents/src/components/Instructions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import type React from "react";

import {
Text,
Box,
Button,
Link,
useDisclosure,
Heading,
} from "@chakra-ui/react";

import InstructionsModal from "./InstructionsModal";
import { useState, useEffect } from "react";

import { usePluginContext } from "@cortexapps/plugin-core/components";

const Instructions: React.FC = () => {
const { apiBaseUrl } = usePluginContext();
const {
isOpen: isInstuctionsModalOpen,
onOpen: onInstructionsModalOpen,
onClose: onInstructionsModalClose,
} = useDisclosure();

const [isMarketplacePlugin, setIsMarketplacePlugin] = useState<
boolean | null
>(null);
const [configCompleted, setConfigCompleted] = useState(false);

useEffect(() => {
const fetchMarketplacePlugin = async (): Promise<void> => {
try {
const response = await fetch(
`${apiBaseUrl}/plugins/pagerduty-incidents`
);
const { description } = await response.json();
setIsMarketplacePlugin(
description.includes(
"https://plugin-marketplace.s3.us-east-2.amazonaws.com/pagerduty-plugin/ui.html"
)
);
} catch (e) {
setIsMarketplacePlugin(false);
}
};
void fetchMarketplacePlugin();
}, [apiBaseUrl]);

const onConfigCompleted = (): void => {
setConfigCompleted(true);
};

if (configCompleted) {
return (
<Box
minH="400px"
backgroundColor="light"
margin={2}
padding={4}
borderRadius={2}
>
<Heading as="h2" size="md" marginBottom={4}>
Configure PagerDuty Incidents Plugin
</Heading>
<Text>
Configuration completed successfully. Please refresh the page to start
using the plugin.
</Text>
</Box>
);
}

return (
<Box
minH="400px"
backgroundColor="light"
margin={2}
padding={4}
borderRadius={2}
>
<Heading as="h2" size="md" marginBottom={4}>
Configure PagerDuty Incidents Plugin
</Heading>
{isMarketplacePlugin && (
<>
<Text>
To configure this plugin automatically, you need a PagerDuty API key
and permissions to create proxies and secrets in Cortex. Click the
button below to enter your PagerDuty API key and do automatic
configuration.
</Text>
<Button onClick={onInstructionsModalOpen} colorScheme="purple">
Configure
</Button>
<InstructionsModal
isOpen={isInstuctionsModalOpen}
onClose={onInstructionsModalClose}
onConfigCompleted={onConfigCompleted}
/>
</>
)}
{isMarketplacePlugin === false && (
<Text>
This plugin was not installed by the Plugin Marketplace, so it cannot
be configured automatically. To configure it manually, follow the
instructions in the plugin documentation{" "}
<Link
display="inline"
target="_blank"
href="https://github.com/cortexapps/cortex-plugins/blob/master/plugins/pagerduty-incidents/README.md"
>
here
</Link>
.
</Text>
)}
</Box>
);
};

export default Instructions;
Loading
Loading