Skip to content

Commit b78f809

Browse files
authored
remove hardcoded URL, add instructions, fix tests (#35)
1 parent eb5dd7b commit b78f809

File tree

6 files changed

+295
-128
lines changed

6 files changed

+295
-128
lines changed

plugins/confluence-plugin/README.md

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,42 @@ This plugin makes it possible to view Confluence assets alongside related entiti
66

77
This is a [Cortex](https://www.cortex.io/) plugin. To see how to run the plugin inside of Cortex, see [our docs](https://docs.cortex.io/docs/plugins).
88

9-
### Prerequisites
9+
## Setup
1010

11-
The URL of your Confluence instance should be configured in `src/components/PageContent.tsx`. To do so, change the following line to match the URL of your server:
11+
### Find your Confluence instance URL
1212

13-
```
14-
const baseConfluenceUrl = "https://confluence-server.atlassian.net";
15-
```
13+
Your Confluence instance URL should look like `https://something.atlassian.net`. We will use this URL in the plugin's proxy and in the plugin's configuration entity.
14+
15+
### Set your Confluence credentials
16+
17+
If you are using username and password authentication, type them in to a text editor with a colon (`:`) between them. If you are using SSO, use an API token in place of the password. To create an API token in Confluence, follow the [instructions provided by Atlassian](https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/). Once you have your text in your text editor like `[email protected]:MySecretPassWordOrToken`, you need to base64 encode it. You can do this using an online tool like [this](https://www.base64encode.org). You will add the base64 encoded value to Cortex as a secret. Copy the base64 encoded value. In Cortex, click on Settings > Secrets > Add secret. In Secret name, type `confluence_secret`, paste the base64 encoded secret into Secret value, and click on Create secret.
18+
19+
### Set up a Plugin Proxy
20+
21+
After you've saved the secret, we will set up a proxy to use that secret to communicate with Confluence. In Cortex, click on Plugins > Proxies > Create Proxy. In Name, type `Confluence Proxy`. Click on Add URL, and put in your Confluence Instance URL, like `https://something.atlassian.net`. Click Save. Next, click on "Add header to https://something.atlassian.net". In the dialog box that appears, type `Authorization` in the Name field, and in the Value field, type `Basic {{{secrets.confluence_secret}}}`. Make sure you include the three curly braces, and make sure the secret name matches the secret you created above, with `secrets.` in front of it. Click Save, then click on the Create Proxy button.
22+
23+
### Associate the plugin with the plugin proxy
1624

17-
Developing and building this plugin requires either [yarn](https://classic.yarnpkg.com/lang/en/docs/install/) or [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).
25+
Create or edit your Confluence Plugin. In the dropdown under the Associated Proxy heading, choose the Confluence proxy you created above. Next, click Save plugin at the bottom of the page.
1826

19-
## Getting started
27+
### Create a plugin configuration entity
2028

21-
1. Modify the `src/components/PageContent.tsx` file as described above
22-
2. Run `yarn` to download all dependencies
23-
3. Run `yarn build` to compile the plugin code into `./dist/ui.html`
24-
4. Upload `ui.html` into Cortex on a create or edit plugin page
25-
5. Add or update the code and repeat steps 2-3 as necessary
29+
- Consider creating a new entity type, so that any existing scorecards are not affected by ths configuration entity. In this example, we have created a new entity type called `plugin-configuration`
30+
- Create a new entity with the tag `confluence-plugin-config`
31+
- Set `x-cortex-definition.confluence-url` to the value of your Confluence Instance URL. For example, if my Confluence Instance URL was `https://martindstone.service-now.com`, my `confluence-plugin-config` entity would look like this:
2632

27-
## Adding Confluence Content
33+
```
34+
openapi: 3.0.1
35+
info:
36+
title: Confluence Plugin Config
37+
description: ""
38+
x-cortex-tag: confluence-plugin-config
39+
x-cortex-type: plugin-configuration
40+
x-cortex-definition:
41+
confluence-url: https://martindstone.service-now.com
42+
```
43+
44+
### Adding Confluence content to entities
2845

2946
Entities can be associated with Confluence Page IDs by adding a PageID tag to the `x-cortex-confluence` object. In the Entity yaml for the entity to the Page ID that you'd like to view inside of Cortex, such as:
3047

@@ -34,3 +51,13 @@ x-cortex-confluence:
3451
```
3552

3653
You can do this for Custom Entities, as well as any Service entity.
54+
55+
### Done!
56+
57+
Now when you load the Confluence plugin on an entity that has a Confluence page ID in `x-cortex-confluence.pageID`, you should see the content of that page in Cortex!
58+
59+
## Building the plugin
60+
61+
1. Run `yarn` to download all dependencies
62+
2. Run `yarn build` to compile the plugin code into `./dist/ui.html`
63+
3. Upload `ui.html` into Cortex on a create or edit plugin page
Lines changed: 30 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,40 @@
1-
import { render, screen, waitFor } from "@testing-library/react";
1+
import { render, waitFor } from "@testing-library/react";
2+
import fetchMock from "jest-fetch-mock";
23
import App from "./App";
34

4-
const mockPageContent = {
5-
id: "131073",
6-
type: "page",
7-
status: "current",
8-
title: "AppDirect Runbook",
9-
macroRenderedOutput: {},
10-
body: {
11-
view: {
12-
value:
13-
'<p><style>[data-colorid=kggi8475gd]{color:#bf2600} html[data-color-mode=dark] [data-colorid=kggi8475gd]{color:#ff6640}</style><strong>This document describes what we do when it breaks!</strong></p><p><br />Step 1 - <span data-colorid="kggi8475gd">EVERYBODY</span> PANIC!!!!!!!</p><p>Step2 - Run to the hills</p><p>Step3 - Run for your lives</p><p /><h2 id="AppDirectRunbook-ArchitecturalDigram">Architectural Digram</h2><span class="confluence-embedded-file-wrapper image-center-wrapper confluence-embedded-manual-size"><img class="confluence-embedded-image image-center" alt="cloud_infrastructure (3).png" width="760" loading="lazy" src="https://cortex-se-test.atlassian.net/wiki/download/thumbnails/131073/cloud_infrastructure%20(3).png?version=1&amp;modificationDate=1706209753278&amp;cacheVersion=1&amp;api=v2&amp;width=760&amp;height=888" data-image-src="https://cortex-se-test.atlassian.net/wiki/download/attachments/131073/cloud_infrastructure%20(3).png?version=1&amp;modificationDate=1706209753278&amp;cacheVersion=1&amp;api=v2" data-height="1632" data-width="1396" data-unresolved-comment-count="0" data-linked-resource-id="2523157" data-linked-resource-version="1" data-linked-resource-type="attachment" data-linked-resource-default-alias="cloud_infrastructure (3).png" data-base-url="https://cortex-se-test.atlassian.net/wiki" data-linked-resource-content-type="image/png" data-linked-resource-container-id="131073" data-linked-resource-container-version="4" data-media-id="d04b592d-798d-486e-80af-6af581929d0b" data-media-type="file" srcset="https://cortex-se-test.atlassian.net/wiki/download/thumbnails/131073/cloud_infrastructure%20(3).png?version=1&amp;modificationDate=1706209753278&amp;cacheVersion=1&amp;api=v2&amp;width=1095&amp;height=1280 2x, https://cortex-se-test.atlassian.net/wiki/download/thumbnails/131073/cloud_infrastructure%20(3).png?version=1&amp;modificationDate=1706209753278&amp;cacheVersion=1&amp;api=v2&amp;width=760&amp;height=888 1x" /></span><p />',
14-
representation: "view",
15-
_expandable: {
16-
webresource: "",
17-
embeddedContent: "",
18-
mediaToken: "",
19-
content: "/rest/api/content/131073",
20-
},
21-
},
22-
_expandable: {
23-
editor: "",
24-
atlas_doc_format: "",
25-
export_view: "",
26-
styled_view: "",
27-
dynamic: "",
28-
storage: "",
29-
editor2: "",
30-
anonymous_export_view: "",
31-
},
32-
},
33-
};
34-
35-
const serviceYaml = {
36-
info: {
37-
"x-cortex-confluence": { pageID: 131073 },
38-
},
39-
};
5+
import { successMockBodies } from "../mocks/mockBodies";
406

417
describe("App", () => {
42-
fetchMock.mockResponses(
43-
[JSON.stringify(serviceYaml), { status: 200 }],
44-
[JSON.stringify(mockPageContent), { status: 200 }]
45-
);
8+
beforeEach(() => {
9+
fetchMock.resetMocks();
10+
});
4611

4712
it("verifies that the plugin works", async () => {
48-
render(<App />);
13+
fetchMock.mockResponse(async (req) => {
14+
const url = req.url.split("?")[0];
15+
if (!successMockBodies[url]) {
16+
return { status: 404 };
17+
}
18+
return {
19+
status: 200,
20+
body: JSON.stringify(successMockBodies[url]),
21+
};
22+
});
23+
24+
const { getByText } = render(<App />);
4925
await waitFor(() => {
50-
expect(screen.queryByText("AppDirect Runbook")).toBeInTheDocument();
26+
expect(fetch).toHaveBeenCalledWith(
27+
"https://api.cortex.dev/catalog/confluence-plugin-config/openapi"
28+
);
29+
expect(fetch).toHaveBeenCalledWith(
30+
"https://api.cortex.dev/catalog/inventory-planner/openapi"
31+
);
32+
expect(fetch).toHaveBeenCalledWith(
33+
expect.stringMatching(
34+
/https:\/\/unit-testing-confluence-instance.atlassian.net.*131073.*/
35+
)
36+
);
37+
expect(getByText("AppDirect Runbook")).toBeInTheDocument();
5138
});
5239
});
5340
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type React from "react";
2+
3+
import { Text, Box } from "@cortexapps/plugin-core/components";
4+
5+
const Instructions: React.FC = () => (
6+
<Box backgroundColor="light" margin={2} padding={4} borderRadius={2}>
7+
<Text>
8+
This plugin makes it possible to view Confluence assets associated with an
9+
entity.
10+
</Text>
11+
<Text>
12+
To get started, please add an entity to Cortex like the following:
13+
</Text>
14+
<Box marginTop={4} padding={4} backgroundColor="white" borderRadius={2}>
15+
<pre>
16+
{`openapi: 3.0.1
17+
info:
18+
title: Confluence Plugin Config
19+
x-cortex-tag: confluence-plugin-config
20+
x-cortex-type: pluginconfiguration
21+
x-cortex-definition:
22+
confluence-url: https://YOUR_INSTANCE.atlassian.net`}
23+
</pre>
24+
</Box>
25+
</Box>
26+
);
27+
28+
export default Instructions;

plugins/confluence-plugin/src/components/PageContent.test.tsx

Lines changed: 34 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,45 @@
11
import { render, screen, waitFor } from "@testing-library/react";
22
import PageContent from "./PageContent";
33

4-
const mockPageContent = {
5-
id: "131073",
6-
type: "page",
7-
status: "current",
8-
title: "AppDirect Runbook",
9-
macroRenderedOutput: {},
10-
body: {
11-
view: {
12-
value:
13-
'<p><style>[data-colorid=kggi8475gd]{color:#bf2600} html[data-color-mode=dark] [data-colorid=kggi8475gd]{color:#ff6640}</style><strong>This document describes what we do when it breaks!</strong></p><p><br />Step 1 - <span data-colorid="kggi8475gd">EVERYBODY</span> PANIC!!!!!!!</p><p>Step2 - Run to the hills</p><p>Step3 - Run for your lives</p><p /><h2 id="AppDirectRunbook-ArchitecturalDigram">Architectural Digram</h2><span class="confluence-embedded-file-wrapper image-center-wrapper confluence-embedded-manual-size"><img class="confluence-embedded-image image-center" alt="cloud_infrastructure (3).png" width="760" loading="lazy" src="https://cortex-se-test.atlassian.net/wiki/download/thumbnails/131073/cloud_infrastructure%20(3).png?version=1&amp;modificationDate=1706209753278&amp;cacheVersion=1&amp;api=v2&amp;width=760&amp;height=888" data-image-src="https://cortex-se-test.atlassian.net/wiki/download/attachments/131073/cloud_infrastructure%20(3).png?version=1&amp;modificationDate=1706209753278&amp;cacheVersion=1&amp;api=v2" data-height="1632" data-width="1396" data-unresolved-comment-count="0" data-linked-resource-id="2523157" data-linked-resource-version="1" data-linked-resource-type="attachment" data-linked-resource-default-alias="cloud_infrastructure (3).png" data-base-url="https://cortex-se-test.atlassian.net/wiki" data-linked-resource-content-type="image/png" data-linked-resource-container-id="131073" data-linked-resource-container-version="4" data-media-id="d04b592d-798d-486e-80af-6af581929d0b" data-media-type="file" srcset="https://cortex-se-test.atlassian.net/wiki/download/thumbnails/131073/cloud_infrastructure%20(3).png?version=1&amp;modificationDate=1706209753278&amp;cacheVersion=1&amp;api=v2&amp;width=1095&amp;height=1280 2x, https://cortex-se-test.atlassian.net/wiki/download/thumbnails/131073/cloud_infrastructure%20(3).png?version=1&amp;modificationDate=1706209753278&amp;cacheVersion=1&amp;api=v2&amp;width=760&amp;height=888 1x" /></span><p />',
14-
representation: "view",
15-
_expandable: {
16-
webresource: "",
17-
embeddedContent: "",
18-
mediaToken: "",
19-
content: "/rest/api/content/131073",
20-
},
21-
},
22-
_expandable: {
23-
editor: "",
24-
atlas_doc_format: "",
25-
export_view: "",
26-
styled_view: "",
27-
dynamic: "",
28-
storage: "",
29-
editor2: "",
30-
anonymous_export_view: "",
31-
},
32-
},
33-
};
34-
35-
const serviceYaml = {
36-
info: {
37-
"x-cortex-confluence": { pageID: 131073 },
38-
},
39-
};
4+
import { successMockBodies, noEntityMockBodies } from "../mocks/mockBodies";
405

416
describe("PageContent", () => {
427
beforeEach(() => {
438
fetchMock.resetMocks();
449
});
4510

4611
it("shows message when no page is found", async () => {
47-
fetchMock.mockResponses(
48-
[JSON.stringify(serviceYaml), { status: 200 }],
49-
[JSON.stringify({}), { status: 500 }]
50-
);
12+
fetchMock.mockResponse(async (req) => {
13+
const url = req.url.split("?")[0];
14+
if (!noEntityMockBodies[url]) {
15+
return { status: 404 };
16+
}
17+
return {
18+
status: 200,
19+
body: JSON.stringify(noEntityMockBodies[url]),
20+
};
21+
});
5122

5223
render(<PageContent />);
5324

5425
await waitFor(() => {
5526
expect(
56-
screen.getByText(
57-
"We could not find any Confluence page associated with this entity"
58-
)
27+
screen.getByText("No Confluence details exist on this entity.")
5928
).toBeInTheDocument();
6029
});
6130
});
6231

6332
it("shows a page if page is found", async () => {
64-
fetchMock.mockResponses(
65-
[JSON.stringify(serviceYaml), { status: 200 }],
66-
[JSON.stringify(mockPageContent), { status: 200 }]
67-
);
33+
fetchMock.mockResponse(async (req) => {
34+
const url = req.url.split("?")[0];
35+
if (!successMockBodies[url]) {
36+
return { status: 404 };
37+
}
38+
return {
39+
status: 200,
40+
body: JSON.stringify(successMockBodies[url]),
41+
};
42+
});
6843

6944
render(<PageContent />);
7045
await waitFor(() => {
@@ -79,18 +54,24 @@ describe("PageContent", () => {
7954

8055
await waitFor(() => {
8156
expect(
82-
screen.getByText(
83-
"We could not find any Confluence page associated with this entity"
57+
screen.queryByText(
58+
"To get started, please add an entity to Cortex like the following:"
8459
)
8560
).toBeInTheDocument();
8661
});
8762
});
8863

8964
it("renders content with dangerouslySetInnerHTML", async () => {
90-
fetchMock.mockResponses(
91-
[JSON.stringify(serviceYaml), { status: 200 }],
92-
[JSON.stringify(mockPageContent), { status: 200 }]
93-
);
65+
fetchMock.mockResponse(async (req) => {
66+
const url = req.url.split("?")[0];
67+
if (!successMockBodies[url]) {
68+
return { status: 404 };
69+
}
70+
return {
71+
status: 200,
72+
body: JSON.stringify(successMockBodies[url]),
73+
};
74+
});
9475

9576
render(<PageContent />);
9677
await waitFor(() => {

0 commit comments

Comments
 (0)