asciidoctor-jira 3.2.0 → 3.3.1

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.
@@ -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(jiraIssuesBlockMacro(context))
243
- this.blockMacro(roadmapBlockMacro('roadmap', context))
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(jiraIssuesBlockMacro(context))
248
- registry.blockMacro(roadmapBlockMacro('roadmap', context))
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
+ }