Skip to content

Commit 3a7b4b4

Browse files
committed
feat:
Documentation Create a template configuration document directly from confluence-outdated
1 parent 4ddf80e commit 3a7b4b4

File tree

8 files changed

+298
-2
lines changed

8 files changed

+298
-2
lines changed

.travis.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
language: node_js
2+
node_js:
3+
- node
4+
- lts/*

README.md

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,74 @@
22

33
## Introduction
44

5-
Tell something about your typescript module.
5+
_confluence-outdated_ searches a Confluence space for documents that haven't been updated for a specified time and
6+
notifies the author of the last version of each document or a separate page maintainer.
67

7-
## Building
8+
## Installation
9+
10+
Install _confluence-outdated_ globally running
11+
12+
npm install -g confluence-outdated
13+
14+
## Configuration Document
15+
16+
_confluence-outdated_ needs several things configured before it can start working. To simplify this,
17+
_confluence-outdated_ reads its configuration from a Confluence document.
18+
19+
The structure of this document is based on Panels and tables. To ease the creation of this document,
20+
_confluence-outdated_ includes a command to create a template document:
21+
22+
confluence-outdated createconfigurationdocument --url <Confluence base URL> --user <Username> --password <Password> --space <Key of space that should hold the document> --title <Title for the configuration document> --parentId <Page ID that the configuration document is place under>
23+
24+
Example:
25+
26+
confluence-outdated createconfigurationdocument --url https://example.com/confluence --user somebody --password secret --space CM --title "My configuration document" --parentId 12345
27+
28+
The command will output the page ID for the configuration document and a link to it.
29+
30+
The configuration is based on several panels. The configuration for the different panels is as follows:
31+
32+
### Panel "Configuration"
33+
34+
- Space: The key of the space where _confluence-outdated_ should check for outdated documents
35+
- Domain: A default domain, that will be appended to all author usernames (should be set if the usernames aren't email addresses themselves)
36+
- NotificationFrom: <The address used as the sender in the notification mails
37+
38+
### Panel "Checks"
39+
40+
A table of checks that will be carried out.
41+
42+
- Labels: A list of all labels (separated by ,) that a checked document has to match
43+
- MaxAge: The maximum age of a checked document. If a document was modified before that, a notification is send
44+
45+
### Panel "Maintainer"
46+
47+
A table of maintainers for pages.
48+
49+
- PagePattern: A regular expression that is matched against the document title
50+
- Maintainer: The user that should receive all notifications for pages matching this pattern
51+
52+
### Panel "Notification Template"
53+
54+
This panel includes two child panels which hold [Handlebars](https://handlebarsjs.com/guide/) templates for the
55+
subject and the body of the notification mails.
56+
57+
They will get [this object](https://github.com/dodevops/confluence-outdated/blob/master/lib/api/DocumentInfo.ts#L6) as
58+
a context for the template.
59+
60+
## Development
61+
62+
The tests subdirectory contain unit tests run by mocha for all parts of the api. If you want to help developing, please
63+
follow the following workflow:
64+
65+
- Create an issue describing the bug or feature
66+
- For this repository
67+
- Create a branch named "issue-<issue number>"
68+
- Write a test that tests the feature or bug
69+
- Run the test => the new test should fail
70+
- Write or fix the code for the change
71+
- Run the test again => the new test should succeed
72+
- Push and create a pull request
873

974
To test and build this package, simply use grunt:
1075

lib/api/Confluence.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import log = require('loglevel')
55
import { ConfluenceError } from '../error/ConfluenceError'
66
import { DocumentInfo } from './DocumentInfo'
77
import { Moment } from 'moment'
8+
import * as fs from 'fs'
9+
import * as path from 'path'
810

911
/**
1012
* Confluence API
@@ -120,4 +122,35 @@ export class Confluence {
120122

121123
return documentInfo
122124
}
125+
126+
public async createConfigurationDocument(space: string, title: string, parentId: string): Promise<string> {
127+
const template = await fs.promises.readFile(path.join(__dirname, '..', '..', 'resources', 'configurationDocument.html'), 'utf-8')
128+
129+
const response = await got
130+
.post(`${this.confluenceUrl}/rest/api/content`, {
131+
json: {
132+
type: 'page',
133+
title: title,
134+
space: {
135+
key: space,
136+
},
137+
ancestors: [
138+
{
139+
id: parentId,
140+
},
141+
],
142+
body: {
143+
storage: {
144+
value: template,
145+
representation: 'storage',
146+
},
147+
},
148+
},
149+
username: this.confluenceUser,
150+
password: this.confluencePassword,
151+
})
152+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
153+
.json<any>()
154+
return response.id
155+
}
123156
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { command, Command, metadata, option } from 'clime'
2+
import { Confluence } from '../api/Confluence'
3+
import { DefaultOptions } from '../DefaultOptions'
4+
5+
export class CheckOptions extends DefaultOptions {
6+
@option({
7+
name: 'space',
8+
flag: 's',
9+
description: 'Space where the document should be',
10+
required: true,
11+
})
12+
space: string
13+
14+
@option({
15+
name: 'title',
16+
flag: 't',
17+
description: 'Title of the configuration document',
18+
required: true,
19+
})
20+
title: string
21+
22+
@option({
23+
name: 'parentId',
24+
flag: 'P',
25+
description: 'Id of a parent page under which the configuration document should be placed',
26+
required: true,
27+
})
28+
parentId: string
29+
}
30+
31+
@command({
32+
description: 'Create a new empty configuration document',
33+
})
34+
export default class extends Command {
35+
@metadata
36+
public async execute(options: CheckOptions): Promise<string> {
37+
const log = options.getLogger()
38+
39+
log.info('Checking for outdated documents')
40+
41+
const confluence = new Confluence(options.confluenceUrl, options.confluenceUser, options.confluencePassword)
42+
43+
const pageId = confluence.createConfigurationDocument(options.space, options.title, options.parentId)
44+
45+
return `The configuration document was created with the ID
46+
${pageId}
47+
Check it out at ${options.confluenceUrl}/pages/viewpage.action?pageId=${pageId}`
48+
}
49+
}

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
"type": "git",
1515
"url": ""
1616
},
17+
"scripts": {
18+
"test": "grunt test"
19+
},
1720
"files": [
1821
"index.d.ts",
1922
"index.js",
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<ac:structured-macro ac:name="panel" ac:schema-version="1" ac:macro-id="4671afbe-d914-470a-bb9e-8b7321f60f79">
2+
<ac:parameter ac:name="title">Configuration</ac:parameter>
3+
<ac:rich-text-body>
4+
<table class="wrapped">
5+
<colgroup>
6+
<col/>
7+
<col/>
8+
</colgroup>
9+
<tbody>
10+
<tr>
11+
<th>Space</th>
12+
<td><i>Enter the space where you want to check</i></td>
13+
</tr>
14+
<tr>
15+
<th>Domain</th>
16+
<td><i>If you're not using e-mail addresses as usernames, setup your default mail domain here</i></td>
17+
</tr>
18+
<tr>
19+
<th>NotificationFrom</th>
20+
<td><i>Enter the From mail address used for notifications here</i></td>
21+
</tr>
22+
</tbody>
23+
</table>
24+
</ac:rich-text-body>
25+
</ac:structured-macro>
26+
<ac:structured-macro ac:name="panel" ac:schema-version="1" ac:macro-id="ecfe796e-b701-4f30-a74a-b94dbb33daff">
27+
<ac:parameter ac:name="title">SMTP</ac:parameter>
28+
<ac:rich-text-body>
29+
<i>See <a href="https://nodemailer.com/smtp/">the nodemailer SMTP documentation</a> for all available options here.</i>
30+
<table class="wrapped">
31+
<colgroup>
32+
<col/>
33+
<col/>
34+
</colgroup>
35+
<tbody>
36+
<tr>
37+
<th>Host</th>
38+
<td>localhost</td>
39+
</tr>
40+
<tr>
41+
<th>Port</th>
42+
<td>25</td>
43+
</tr>
44+
</tbody>
45+
</table>
46+
</ac:rich-text-body>
47+
</ac:structured-macro>
48+
<ac:structured-macro ac:name="panel" ac:schema-version="1" ac:macro-id="f19cd8b2-57e0-4c68-a823-8a2daee08c12">
49+
<ac:parameter ac:name="title">Checks</ac:parameter>
50+
<ac:rich-text-body>
51+
<i>This panel includes a list of checks to carry out. The script will search for a list of labels (separated by ,), which a document candidate must have and a maximum age in days for that document.</i>
52+
<table class="wrapped">
53+
<colgroup>
54+
<col/>
55+
<col/>
56+
</colgroup>
57+
<tbody>
58+
<tr>
59+
<th>Labels</th>
60+
<th>MaxAge</th>
61+
</tr>
62+
<tr>
63+
<td></td>
64+
<td></td>
65+
</tr>
66+
</tbody>
67+
</table>
68+
</ac:rich-text-body>
69+
</ac:structured-macro>
70+
<ac:structured-macro ac:name="panel" ac:schema-version="1" ac:macro-id="1d192d60-7e69-4af8-8dd6-4006a7bfc952">
71+
<ac:parameter ac:name="title">Maintainer</ac:parameter>
72+
<ac:rich-text-body>
73+
<i>This table holds a list of RegExp patterns for page titles and the associated maintainer for this pages, which will be notified instead of the last version's author.</i>
74+
<table class="wrapped">
75+
<colgroup>
76+
<col/>
77+
<col/>
78+
</colgroup>
79+
<tbody>
80+
<tr>
81+
<th>PagePattern</th>
82+
<th>Maintainer</th>
83+
</tr>
84+
<tr>
85+
<td></td>
86+
<td></td>
87+
</tr>
88+
</tbody>
89+
</table>
90+
</ac:rich-text-body>
91+
</ac:structured-macro>
92+
<ac:structured-macro ac:name="panel" ac:schema-version="1" ac:macro-id="93f1d981-c841-4cb4-b6e2-5940dfe69132">
93+
<ac:parameter ac:name="title">Notification Template</ac:parameter>
94+
<ac:rich-text-body>
95+
<i>The two panels inside of this panel configure the subject and body template for the notifications.
96+
These are <a href="https://handlebarsjs.com/guide/">Handlebars</a> templates, which get
97+
<a href="https://github.com/dodevops/confluence-outdated/blob/master/lib/api/DocumentInfo.ts#L6">this object</a>
98+
as their context.</i>
99+
<ac:structured-macro ac:name="panel" ac:schema-version="1" ac:macro-id="f8503e48-c671-4ed6-897c-def2b2c3fa29">
100+
<ac:parameter ac:name="title">Subject</ac:parameter>
101+
<ac:rich-text-body>
102+
<p>Dokument outdated: {{title}}</p></ac:rich-text-body>
103+
</ac:structured-macro>
104+
<ac:structured-macro ac:name="panel" ac:schema-version="1" ac:macro-id="63c16112-dea3-434e-b1cb-467ff4e36d5f">
105+
<ac:parameter ac:name="title">Body</ac:parameter>
106+
<ac:rich-text-body>
107+
<p>Greetings {{author}},</p>
108+
<p>The most recent change of the document</p>
109+
<p>{{title}}</p>
110+
<p>was at {{lastVersionDate}}.</p>
111+
<p>{{#if lastVersionMessage}}</p>
112+
<p>The edit comment used was: {{lastVersionMessage}}</p>
113+
<p>{{/if}}</p>
114+
<p>You can find the document here: {{url}}</p>
115+
<p>Please check, wether the document needs any updates or save the document again stating that it is still valid.</p>
116+
<p>Cheers, confluence-outdated</p></ac:rich-text-body>
117+
</ac:structured-macro>
118+
</ac:rich-text-body>
119+
</ac:structured-macro>

test/ConfluenceTest.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,12 @@ describe('The Confluence API', (): void => {
2828
chai.expect(results[1].lastVersionMessage).to.eq('')
2929
chai.expect(results[1].title).to.eq('Test2')
3030
})
31+
32+
it('should add a configuration document', async (): Promise<void> => {
33+
const mockServer = new MockServer('https://example.com')
34+
mockServer.addCreateEndpoint()
35+
const confluence = new Confluence('https://example.com', 'nobody', 'nothing')
36+
const result = await confluence.createConfigurationDocument('example', 'test', '0123')
37+
chai.expect(result).to.eq('12345')
38+
})
3139
})

test/MockServer.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,4 +217,19 @@ export class MockServer {
217217
title: 'Test2',
218218
})
219219
}
220+
221+
public addCreateEndpoint(): void {
222+
this._scope
223+
.post('/rest/api/content')
224+
.basicAuth({
225+
user: 'nobody',
226+
pass: 'nothing',
227+
})
228+
.reply(200, (uri, requestBody) => {
229+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
230+
const requestObject = requestBody as Record<string, any>
231+
requestObject.id = '12345'
232+
return requestObject
233+
})
234+
}
220235
}

0 commit comments

Comments
 (0)