asciidoctor-jira 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/asciidoctor-jira.js +6 -233
- package/lib/blockmacro/jiraIssuesBlockmacro.js +92 -0
- package/lib/blockmacro/roadmapBlockmacro.js +126 -0
- package/lib/blockmacro/roadmapBlockmacroConfig.js +447 -0
- package/lib/common/jiraConfig.js +416 -0
- package/lib/common/utils.js +9 -0
- package/lib/inlinemacro/jiraIssueInlineMacro.js +29 -0
- package/package.json +4 -4
- package/lib/roadmap/config.js +0 -616
package/lib/asciidoctor-jira.js
CHANGED
|
@@ -1,235 +1,8 @@
|
|
|
1
1
|
/* global
|
|
2
2
|
Opal
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
4
|
// @ts-check
|
|
6
|
-
const plantumlEncoder = require('plantuml-encoder')
|
|
7
|
-
const Jira = require('./Jira.js')
|
|
8
5
|
require('dotenv').config()
|
|
9
|
-
const _ = require('lodash')
|
|
10
|
-
const JIRA = require('./Jira')
|
|
11
|
-
const rusha = require('rusha')
|
|
12
|
-
|
|
13
|
-
function jiraIssuesBlockMacro (context) {
|
|
14
|
-
return function () {
|
|
15
|
-
const self = this
|
|
16
|
-
self.named('jira')
|
|
17
|
-
self.positionalAttributes(['jql'])
|
|
18
|
-
self.process((parent, target, attrs) => {
|
|
19
|
-
const doc = parent.getDocument()
|
|
20
|
-
const jiraProject = parent.applySubstitutions(target, ['attributes'])
|
|
21
|
-
const jql = attrs.jql || (jiraProject === undefined ? 'resolution="Unresolved" ORDER BY priority DESC, key ASC, duedate ASC' : `project = ${jiraProject} AND resolution="Unresolved" ORDER BY priority DESC, key ASC, duedate ASC`)
|
|
22
|
-
const customFields = attrs.customFieldIds || 'priority,created,assignee,issuetype,summary'
|
|
23
|
-
const customFieldIds = customFields.split(',').map(customField => customField.split('.')[0])
|
|
24
|
-
const jiraClient = new Jira(doc, doc.getAttribute('jira-username') || process.env.JIRA_USERNAME, doc.getAttribute('jira-apitoken') || process.env.JIRA_APITOKEN, doc.getAttribute('jira-baseurl') || process.env.JIRA_BASEURL)
|
|
25
|
-
const issues = jiraClient.searchIssues(jql, customFieldIds)
|
|
26
|
-
const logger = context.logger
|
|
27
|
-
|
|
28
|
-
const headers = createHeaders(doc, customFields)
|
|
29
|
-
const customFieldsArray = customFields.split(',').filter(item => item !== 'issuetype')
|
|
30
|
-
|
|
31
|
-
const content = []
|
|
32
|
-
content.push('[options="header"]')
|
|
33
|
-
content.push('|====')
|
|
34
|
-
content.push('|' + headers.join('|'))
|
|
35
|
-
|
|
36
|
-
for (let i = 0; i < issues.length; i++) {
|
|
37
|
-
const issue = issues[i]
|
|
38
|
-
let idColumn = 'a|'
|
|
39
|
-
if (issue.fields.issuetype && customFieldIds.includes('issuetype')) {
|
|
40
|
-
const issueTypeName = issue.fields.issuetype.name
|
|
41
|
-
const issueTypeIconUrl = issue.fields.issuetype.iconUrl
|
|
42
|
-
const imageName = `jira-issuetype-${issueTypeName.toLowerCase()}.svg`
|
|
43
|
-
jiraClient.download(imageName, issueTypeIconUrl, context.vfs)
|
|
44
|
-
idColumn += `image:${imageName}[]`
|
|
45
|
-
}
|
|
46
|
-
idColumn += `${createLinkToIssue(doc, issue.key)}[${issue.key}]`
|
|
47
|
-
content.push(idColumn)
|
|
48
|
-
|
|
49
|
-
for (let j = 0; j < customFieldsArray.length; j++) {
|
|
50
|
-
let value
|
|
51
|
-
if (!_.has(issue.fields, customFieldsArray[j])) {
|
|
52
|
-
logger.warn(`Examining issue '${JSON.stringify(issue, null, 2)}' for custom field '${customFieldsArray[j]}', but was not found.`)
|
|
53
|
-
value = '-'
|
|
54
|
-
} else {
|
|
55
|
-
value = _.get(issue.fields, customFieldsArray[j])
|
|
56
|
-
if (value !== null && (value.constructor === Array)) {
|
|
57
|
-
value = value.toString()
|
|
58
|
-
} else if ((typeof value === 'object') && value != null) {
|
|
59
|
-
value = value.name || value.displayName || doc.getAttribute(`jira-table-${customFieldsArray[j].replace(/\./g, '-')}-default`, '-')
|
|
60
|
-
} else {
|
|
61
|
-
value = value || doc.getAttribute(`jira-table-${customFieldsArray[j].replace(/\./g, '-')}-default`, '-')
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
if (typeof value === 'number') {
|
|
65
|
-
content.push('|' + value)
|
|
66
|
-
} else {
|
|
67
|
-
content.push('|' + value.replace(/\|/g, '\\|'))
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
content.push('|====')
|
|
72
|
-
|
|
73
|
-
self.parseContent(parent, content.join('\n'), Opal.hash(attrs))
|
|
74
|
-
|
|
75
|
-
return undefined
|
|
76
|
-
})
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function createHeaders (doc, customFieldIds) {
|
|
81
|
-
const headers = []
|
|
82
|
-
const customFieldsArray = customFieldIds.split(',').filter(item => item !== 'issuetype')
|
|
83
|
-
headers.push(doc.getAttribute('jira-table-header-id-label', 'ID'))
|
|
84
|
-
for (let i = 0; i < customFieldsArray.length; i++) {
|
|
85
|
-
let field = customFieldsArray[i]
|
|
86
|
-
field = `jira-table-header-${field.replace(/\./g, '-').toLowerCase()}-label`
|
|
87
|
-
headers.push(doc.getAttribute(field, customFieldsArray[i]))
|
|
88
|
-
}
|
|
89
|
-
return headers
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function jiraIssueInlineMacro (context) {
|
|
93
|
-
return function () {
|
|
94
|
-
const self = this
|
|
95
|
-
self.named('jira')
|
|
96
|
-
self.positionalAttributes(['format'])
|
|
97
|
-
self.process((parent, target, attrs) => {
|
|
98
|
-
const doc = parent.getDocument()
|
|
99
|
-
const displayFormat = attrs.format || doc.getAttribute('jira-inline-format') || 'short'
|
|
100
|
-
const issueKey = target
|
|
101
|
-
const jiraClient = new Jira(doc, doc.getAttribute('jira-username') || process.env.JIRA_USERNAME, doc.getAttribute('jira-apitoken') || process.env.JIRA_APITOKEN, doc.getAttribute('jira-baseurl') || process.env.JIRA_BASEURL)
|
|
102
|
-
const issue = jiraClient.searchIssue(issueKey)
|
|
103
|
-
let title = issueKey
|
|
104
|
-
if (displayFormat === 'long') {
|
|
105
|
-
title += issue.fields.summary
|
|
106
|
-
}
|
|
107
|
-
const issueLink = createLinkToIssue(doc, issueKey)
|
|
108
|
-
return self.createInline(parent, 'anchor', title, { type: 'link', target: issueLink }).convert()
|
|
109
|
-
})
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function roadmapBlockMacro (name, context) {
|
|
114
|
-
return function () {
|
|
115
|
-
const self = this
|
|
116
|
-
self.named(name)
|
|
117
|
-
self.positionalAttributes(['year', 'categories', 'release-date'])
|
|
118
|
-
self.process((parent, target, attrs) => {
|
|
119
|
-
const vfs = context.vfs
|
|
120
|
-
const config = require('./roadmap/config').createConfig(name, parent, target, attrs, parent.getDocument().getLogger())
|
|
121
|
-
const doc = parent.getDocument()
|
|
122
|
-
|
|
123
|
-
const jiraProject = config.projectKey
|
|
124
|
-
const categories = config.categories
|
|
125
|
-
const year = config.year
|
|
126
|
-
const jiraBaseUrl = config.jiraBaseUrl
|
|
127
|
-
const jiraFields = config.jiraFields
|
|
128
|
-
const plantUmlServerUrl = config.plantumlServerUrl
|
|
129
|
-
const jiraStatusClosed = config.closedStatus
|
|
130
|
-
const theme = config.plantumlTheme
|
|
131
|
-
const lastRoadmapReleaseDate = config.lastRoadmapReleaseDate
|
|
132
|
-
const legendForStatus = config.legendForStatus
|
|
133
|
-
|
|
134
|
-
const jiraClient = new JIRA(doc, config.jiraUsername, config.jiraPassword, config.jiraBaseUrl)
|
|
135
|
-
|
|
136
|
-
const content = []
|
|
137
|
-
content.push('@startgantt', 'printscale monthly zoom 3', 'language de', `Project starts the 1st of january ${year}`, 'hide footbox')
|
|
138
|
-
if (theme !== null) {
|
|
139
|
-
content.push(`!theme ${theme}`)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
content.push(`<style>
|
|
143
|
-
ganttdiagram {
|
|
144
|
-
milestone {
|
|
145
|
-
FontSize 18
|
|
146
|
-
}
|
|
147
|
-
separator {
|
|
148
|
-
FontSize 18
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
</style>`)
|
|
152
|
-
|
|
153
|
-
for (const catIndex in categories) {
|
|
154
|
-
const categoryLabel = config.getCategoryLabel(catIndex)
|
|
155
|
-
const jql = config.getCategoryJQL(catIndex)
|
|
156
|
-
|
|
157
|
-
const issues = jiraClient.searchIssues(jql, Object.keys(jiraFields).map(it => jiraFields[it]).join(','))
|
|
158
|
-
|
|
159
|
-
if (issues != null && issues.length > 0) {
|
|
160
|
-
// add category label as separator
|
|
161
|
-
content.push(`-- ${categoryLabel} --`)
|
|
162
|
-
|
|
163
|
-
for (const issueIndex in issues) {
|
|
164
|
-
const issue = issues[issueIndex]
|
|
165
|
-
const issueKey = issue.key
|
|
166
|
-
let dueDate = issue.fields[jiraFields.dueDate]
|
|
167
|
-
const status = issue.fields[jiraFields.status].name
|
|
168
|
-
const jiraIssueLink = `${jiraBaseUrl}/browse/${issueKey}`
|
|
169
|
-
const roadmapEntryName = issue.fields[jiraFields.epicName] || issue.fields[jiraFields.summary]
|
|
170
|
-
|
|
171
|
-
const isAfterLastRoadmapReleaseDate = lastRoadmapReleaseDate === null || (new Date(lastRoadmapReleaseDate) < new Date(issue.fields[jiraFields.created]))
|
|
172
|
-
|
|
173
|
-
if (jiraStatusClosed.includes(status)) {
|
|
174
|
-
const resolutionDate = new Date(issue.fields[jiraFields.resolutionDate])
|
|
175
|
-
dueDate = `${resolutionDate.getFullYear()}-${resolutionDate.getMonth() + 1}-${resolutionDate.getDate()}`
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// calculate dua date
|
|
179
|
-
if (dueDate !== undefined) {
|
|
180
|
-
content.push(`[${roadmapEntryName}] as [${issueKey}] happens at ${dueDate}`)
|
|
181
|
-
} else {
|
|
182
|
-
doc.getLogger().warn(`${jiraIssueLink} has no due date, to appear on roadmap this is needed.`)
|
|
183
|
-
continue
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// link to jira issue
|
|
187
|
-
content.push(`[${issueKey}] links to [[${jiraIssueLink}]]`)
|
|
188
|
-
|
|
189
|
-
// render completion status
|
|
190
|
-
if (isAfterLastRoadmapReleaseDate && !jiraStatusClosed.includes(status)) {
|
|
191
|
-
const color4TaskAfterLastRoadmapReleaseDate = attrs[`${name}-milestone-after-last-roadmap-release-date-color`] || doc.getAttribute(`${name}-milestone-after-last-roadmap-release-date-color`) || 'black'
|
|
192
|
-
content.push(`[${issueKey}] is colored in ${color4TaskAfterLastRoadmapReleaseDate}`)
|
|
193
|
-
} else {
|
|
194
|
-
const status4Attribute = status.toLowerCase().replace(' ', '_')
|
|
195
|
-
const color4Task = attrs[`${name}-milestone-${status4Attribute}-color`] || doc.getAttribute(`${name}-milestone-${status4Attribute}-color`) || 'black'
|
|
196
|
-
content.push(`[${issueKey}] is colored in ${color4Task}`)
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
content.push('--')
|
|
202
|
-
|
|
203
|
-
// add legend
|
|
204
|
-
for (const legendIndex in legendForStatus) {
|
|
205
|
-
const legend = legendForStatus[legendIndex]
|
|
206
|
-
const status4Attribute = legend.toLowerCase().replace(' ', '_')
|
|
207
|
-
const color4Task = attrs[`${name}-milestone-${status4Attribute}-color`] || doc.getAttribute(`${name}-milestone-${status4Attribute}-color`) || 'black'
|
|
208
|
-
content.push(`[${legend}] happens at 1st of january ${year} and is colored in ${color4Task}`)
|
|
209
|
-
}
|
|
210
|
-
if (lastRoadmapReleaseDate !== undefined) {
|
|
211
|
-
const releaseDateFormattedForLegend = `${new Date(lastRoadmapReleaseDate).toLocaleDateString('en-US', { day: 'numeric', month: 'short', year: 'numeric' })}`
|
|
212
|
-
content.push(`[Added after initial roadmap from ${releaseDateFormattedForLegend}] happens at 1st of january ${year} and is colored in orange`)
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
content.push('@endgantt')
|
|
216
|
-
|
|
217
|
-
doc.getLogger().debug(`${content.join('\n')}`)
|
|
218
|
-
|
|
219
|
-
const downloadUrl = `${plantUmlServerUrl}/svg/${plantumlEncoder.encode(content.join('\n'))} `
|
|
220
|
-
const diagramName = `roadmap-${jiraProject}-${year}-${rusha.createHash().update(content).digest('hex')}.svg`
|
|
221
|
-
require('./common/fetch.js').save(diagramName, downloadUrl, doc, 'svg', vfs)
|
|
222
|
-
|
|
223
|
-
self.parseContent(parent, `image::${diagramName}[opts=interactive]`, Opal.hash(attrs))
|
|
224
|
-
return undefined
|
|
225
|
-
})
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function createLinkToIssue (doc, issueKey) {
|
|
230
|
-
const jiraBaseUrl = doc.getAttribute('jira-baseurl') || process.env.JIRA_BASEURL
|
|
231
|
-
return `${jiraBaseUrl}/browse/${issueKey}`
|
|
232
|
-
}
|
|
233
6
|
|
|
234
7
|
module.exports.register = function register (registry, context = {}) {
|
|
235
8
|
// patch context in case of Antora
|
|
@@ -239,14 +12,14 @@ module.exports.register = function register (registry, context = {}) {
|
|
|
239
12
|
context.logger = Opal.Asciidoctor.LoggerManager.getLogger()
|
|
240
13
|
if (typeof registry.register === 'function') {
|
|
241
14
|
registry.register(function () {
|
|
242
|
-
this.blockMacro(
|
|
243
|
-
this.blockMacro(
|
|
244
|
-
this.inlineMacro(jiraIssueInlineMacro(context))
|
|
15
|
+
this.blockMacro(require('./blockmacro/jiraIssuesBlockmacro').blockMacro(context))
|
|
16
|
+
this.blockMacro(require('./blockmacro/roadmapBlockmacro').blockMacro('roadmap', context))
|
|
17
|
+
this.inlineMacro(require('./inlinemacro/jiraIssueInlineMacro').inlineMacro(context))
|
|
245
18
|
})
|
|
246
19
|
} else if (typeof registry.block === 'function') {
|
|
247
|
-
registry.blockMacro(
|
|
248
|
-
registry.blockMacro(
|
|
249
|
-
registry.inlineMacro(jiraIssueInlineMacro(context))
|
|
20
|
+
registry.blockMacro(require('./blockmacro/jiraIssuesBlockmacro').blockMacro(context))
|
|
21
|
+
registry.blockMacro(require('./blockmacro/roadmapBlockmacro').blockMacro('roadmap', context))
|
|
22
|
+
registry.inlineMacro(require('./inlinemacro/jiraIssueInlineMacro').inlineMacro(context))
|
|
250
23
|
}
|
|
251
24
|
return registry
|
|
252
25
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/* global
|
|
2
|
+
Opal
|
|
3
|
+
*/
|
|
4
|
+
const Jira = require('../Jira')
|
|
5
|
+
const _ = require('lodash')
|
|
6
|
+
const { createLinkToIssue } = require('../common/utils')
|
|
7
|
+
|
|
8
|
+
function jiraIssuesBlockMacro (context) {
|
|
9
|
+
return function () {
|
|
10
|
+
const self = this
|
|
11
|
+
self.named('jira')
|
|
12
|
+
self.positionalAttributes(['jql'])
|
|
13
|
+
self.process((parent, target, attrs) => {
|
|
14
|
+
const doc = parent.getDocument()
|
|
15
|
+
const jiraProject = parent.applySubstitutions(target, ['attributes'])
|
|
16
|
+
const jql = attrs.jql || (jiraProject === undefined ? 'resolution="Unresolved" ORDER BY priority DESC, key ASC, duedate ASC' : `project = ${jiraProject} AND resolution="Unresolved" ORDER BY priority DESC, key ASC, duedate ASC`)
|
|
17
|
+
const customFields = attrs.customFieldIds || 'priority,created,assignee,issuetype,summary'
|
|
18
|
+
const customFieldIds = customFields.split(',').map(customField => customField.split('.')[0])
|
|
19
|
+
const logger = context.logger
|
|
20
|
+
const jiraConfig = require('../common/jiraConfig').createConfig('', doc, jiraProject, attrs, logger)
|
|
21
|
+
const jiraClient = new Jira(doc, jiraConfig.username, jiraConfig.password, jiraConfig.baseUrl)
|
|
22
|
+
const issues = jiraClient.searchIssues(jql, customFieldIds)
|
|
23
|
+
|
|
24
|
+
const headers = createHeaders(doc, customFields)
|
|
25
|
+
const customFieldsArray = customFields.split(',').filter(item => item !== 'issuetype')
|
|
26
|
+
|
|
27
|
+
const content = []
|
|
28
|
+
content.push('[options="header"]')
|
|
29
|
+
content.push('|====')
|
|
30
|
+
content.push('|' + headers.join('|'))
|
|
31
|
+
|
|
32
|
+
for (let i = 0; i < issues.length; i++) {
|
|
33
|
+
const issue = issues[i]
|
|
34
|
+
let idColumn = 'a|'
|
|
35
|
+
if (issue.fields.issuetype && customFieldIds.includes('issuetype')) {
|
|
36
|
+
const issueTypeName = issue.fields.issuetype.name
|
|
37
|
+
const issueTypeIconUrl = issue.fields.issuetype.iconUrl
|
|
38
|
+
const imageName = `jira-issuetype-${issueTypeName.toLowerCase()}.svg`
|
|
39
|
+
jiraClient.download(imageName, issueTypeIconUrl, context.vfs)
|
|
40
|
+
idColumn += `image:${imageName}[]`
|
|
41
|
+
}
|
|
42
|
+
idColumn += `${createLinkToIssue(jiraConfig.baseUrl, issue.key)}[${issue.key}]`
|
|
43
|
+
content.push(idColumn)
|
|
44
|
+
|
|
45
|
+
for (let j = 0; j < customFieldsArray.length; j++) {
|
|
46
|
+
let value
|
|
47
|
+
if (!_.has(issue.fields, customFieldsArray[j])) {
|
|
48
|
+
logger.warn(`Examining issue '${JSON.stringify(issue, null, 2)}' for custom field '${customFieldsArray[j]}', but was not found.`)
|
|
49
|
+
value = '-'
|
|
50
|
+
} else {
|
|
51
|
+
value = _.get(issue.fields, customFieldsArray[j])
|
|
52
|
+
if (value !== null && (value.constructor === Array)) {
|
|
53
|
+
value = value.toString()
|
|
54
|
+
} else if ((typeof value === 'object') && value != null) {
|
|
55
|
+
value = value.name || value.displayName || doc.getAttribute(`jira-table-${customFieldsArray[j].replace(/\./g, '-')}-default`, '-')
|
|
56
|
+
} else {
|
|
57
|
+
value = value || doc.getAttribute(`jira-table-${customFieldsArray[j].replace(/\./g, '-')}-default`, '-')
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (typeof value === 'number') {
|
|
61
|
+
content.push('|' + value)
|
|
62
|
+
} else {
|
|
63
|
+
content.push('|' + value.replace(/\|/g, '\\|'))
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
content.push('|====')
|
|
68
|
+
|
|
69
|
+
self.parseContent(parent, content.join('\n'), Opal.hash(attrs))
|
|
70
|
+
|
|
71
|
+
return undefined
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function createHeaders (doc, customFieldIds) {
|
|
77
|
+
const headers = []
|
|
78
|
+
const customFieldsArray = customFieldIds.split(',').filter(item => item !== 'issuetype')
|
|
79
|
+
headers.push(doc.getAttribute('jira-table-header-id-label', 'ID'))
|
|
80
|
+
for (let i = 0; i < customFieldsArray.length; i++) {
|
|
81
|
+
let field = customFieldsArray[i]
|
|
82
|
+
field = `jira-table-header-${field.replace(/\./g, '-').toLowerCase()}-label`
|
|
83
|
+
headers.push(doc.getAttribute(field, customFieldsArray[i]))
|
|
84
|
+
}
|
|
85
|
+
return headers
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = {
|
|
89
|
+
blockMacro: (context) => {
|
|
90
|
+
return jiraIssuesBlockMacro(context)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/* global
|
|
2
|
+
Opal
|
|
3
|
+
*/
|
|
4
|
+
const JIRA = require('../Jira')
|
|
5
|
+
const plantumlEncoder = require('plantuml-encoder')
|
|
6
|
+
const rusha = require('rusha')
|
|
7
|
+
|
|
8
|
+
function roadmapBlockMacro (name, context) {
|
|
9
|
+
return function () {
|
|
10
|
+
const self = this
|
|
11
|
+
self.named(name)
|
|
12
|
+
self.positionalAttributes(['year', 'categories', 'release-date'])
|
|
13
|
+
self.process((parent, target, attrs) => {
|
|
14
|
+
const vfs = context.vfs
|
|
15
|
+
const config = require('./roadmapBlockmacroConfig').createConfig(name, parent, target, attrs, parent.getDocument().getLogger())
|
|
16
|
+
const doc = parent.getDocument()
|
|
17
|
+
|
|
18
|
+
const jiraProject = config.projectKey
|
|
19
|
+
const categories = config.categories
|
|
20
|
+
const year = config.year
|
|
21
|
+
const jiraBaseUrl = config.jira.baseUrl
|
|
22
|
+
const jiraFields = config.jiraFields
|
|
23
|
+
const plantUmlServerUrl = config.plantumlServerUrl
|
|
24
|
+
const jiraStatusClosed = config.closedStatus
|
|
25
|
+
const theme = config.plantumlTheme
|
|
26
|
+
const lastRoadmapReleaseDate = config.lastRoadmapReleaseDate
|
|
27
|
+
const legendForStatus = config.legendForStatus
|
|
28
|
+
|
|
29
|
+
const jiraClient = new JIRA(doc, config.jira.username, config.jira.password, config.jira.baseUrl)
|
|
30
|
+
|
|
31
|
+
const content = []
|
|
32
|
+
content.push('@startgantt', 'printscale monthly zoom 3', 'language de', `Project starts the 1st of january ${year}`, 'hide footbox')
|
|
33
|
+
if (theme !== null) {
|
|
34
|
+
content.push(`!theme ${theme}`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
content.push(`<style>
|
|
38
|
+
ganttdiagram {
|
|
39
|
+
milestone {
|
|
40
|
+
FontSize 18
|
|
41
|
+
}
|
|
42
|
+
separator {
|
|
43
|
+
FontSize 18
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
</style>`)
|
|
47
|
+
|
|
48
|
+
for (const catIndex in categories) {
|
|
49
|
+
const categoryLabel = config.getCategoryLabel(catIndex)
|
|
50
|
+
const jql = config.getCategoryJQL(catIndex)
|
|
51
|
+
|
|
52
|
+
const issues = jiraClient.searchIssues(jql, Object.keys(jiraFields).map(it => jiraFields[it]).join(','))
|
|
53
|
+
if (issues != null && issues.length > 0) {
|
|
54
|
+
// add category label as separator
|
|
55
|
+
content.push(`-- ${categoryLabel} --`)
|
|
56
|
+
|
|
57
|
+
for (const issueIndex in issues) {
|
|
58
|
+
const issue = issues[issueIndex]
|
|
59
|
+
const issueKey = issue.key
|
|
60
|
+
let dueDate = issue.fields[jiraFields.dueDate]
|
|
61
|
+
const status = issue.fields[jiraFields.status].name
|
|
62
|
+
const jiraIssueLink = `${jiraBaseUrl}/browse/${issueKey}`
|
|
63
|
+
const roadmapEntryName = issue.fields[jiraFields.epicName] || issue.fields[jiraFields.summary]
|
|
64
|
+
|
|
65
|
+
const isAfterLastRoadmapReleaseDate = lastRoadmapReleaseDate === null || (new Date(lastRoadmapReleaseDate) < new Date(issue.fields[jiraFields.created]))
|
|
66
|
+
|
|
67
|
+
if (jiraStatusClosed.includes(status)) {
|
|
68
|
+
const resolutionDate = new Date(issue.fields[jiraFields.resolutionDate])
|
|
69
|
+
dueDate = `${resolutionDate.getFullYear()}-${resolutionDate.getMonth() + 1}-${resolutionDate.getDate()}`
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// calculate dua date
|
|
73
|
+
if (dueDate !== undefined) {
|
|
74
|
+
content.push(`[${roadmapEntryName}] as [${issueKey}] happens at ${dueDate}`)
|
|
75
|
+
} else {
|
|
76
|
+
doc.getLogger().warn(`${jiraIssueLink} has no due date, to appear on roadmap this is needed.`)
|
|
77
|
+
continue
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// link to jira issue
|
|
81
|
+
content.push(`[${issueKey}] links to [[${jiraIssueLink}]]`)
|
|
82
|
+
|
|
83
|
+
// render completion status
|
|
84
|
+
if (isAfterLastRoadmapReleaseDate && !jiraStatusClosed.includes(status)) {
|
|
85
|
+
const color4TaskAfterLastRoadmapReleaseDate = attrs[`${name}-milestone-after-last-roadmap-release-date-color`] || doc.getAttribute(`${name}-milestone-after-last-roadmap-release-date-color`) || 'black'
|
|
86
|
+
content.push(`[${issueKey}] is colored in ${color4TaskAfterLastRoadmapReleaseDate}`)
|
|
87
|
+
} else {
|
|
88
|
+
const status4Attribute = status.toLowerCase().replace(' ', '_')
|
|
89
|
+
const color4Task = attrs[`${name}-milestone-${status4Attribute}-color`] || doc.getAttribute(`${name}-milestone-${status4Attribute}-color`) || 'black'
|
|
90
|
+
content.push(`[${issueKey}] is colored in ${color4Task}`)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
content.push('--')
|
|
96
|
+
|
|
97
|
+
// add legend
|
|
98
|
+
for (const legendIndex in legendForStatus) {
|
|
99
|
+
const legend = legendForStatus[legendIndex]
|
|
100
|
+
const status4Attribute = legend.toLowerCase().replace(' ', '_')
|
|
101
|
+
const color4Task = attrs[`${name}-milestone-${status4Attribute}-color`] || doc.getAttribute(`${name}-milestone-${status4Attribute}-color`) || 'black'
|
|
102
|
+
content.push(`[${legend}] happens at 1st of january ${year} and is colored in ${color4Task}`)
|
|
103
|
+
}
|
|
104
|
+
if (lastRoadmapReleaseDate !== undefined) {
|
|
105
|
+
const releaseDateFormattedForLegend = `${new Date(lastRoadmapReleaseDate).toLocaleDateString('en-US', { day: 'numeric', month: 'short', year: 'numeric' })}`
|
|
106
|
+
content.push(`[Added after initial roadmap from ${releaseDateFormattedForLegend}] happens at 1st of january ${year} and is colored in orange`)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
content.push('@endgantt')
|
|
110
|
+
|
|
111
|
+
doc.getLogger().info(`${content.join('\n')}`)
|
|
112
|
+
const downloadUrl = `${plantUmlServerUrl}/svg/${plantumlEncoder.encode(content.join('\n'))} `
|
|
113
|
+
const diagramName = `roadmap-${jiraProject}-${year}-${rusha.createHash().update(content).digest('hex')}.svg`
|
|
114
|
+
require('../common/fetch.js').save(diagramName, downloadUrl, doc, 'svg', vfs)
|
|
115
|
+
|
|
116
|
+
self.parseContent(parent, `image::${diagramName}[opts=interactive]`, Opal.hash(attrs))
|
|
117
|
+
return undefined
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = {
|
|
123
|
+
blockMacro: (macroName, context) => {
|
|
124
|
+
return roadmapBlockMacro(macroName, context)
|
|
125
|
+
}
|
|
126
|
+
}
|