Skip to content

Commit f66d704

Browse files
committedFeb 26, 2019
init
0 parents  commit f66d704

12 files changed

+6514
-0
lines changed
 

‎.eslintrc.json

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"extends": "@atlassian-partner-engineering",
3+
"rules": {
4+
"no-plusplus": "off"
5+
}
6+
}
7+

‎Dockerfile

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
FROM node:10-alpine
2+
3+
LABEL "name"="Jira TODO"
4+
LABEL "maintainer"="Dima Rudzik <drudzik+githubactions@atlassian.net>"
5+
LABEL "version"="1.0.0"
6+
7+
LABEL "com.github.actions.name"="Jira TODO"
8+
LABEL "com.github.actions.description"="Create Jira issue for TODO comments"
9+
LABEL "com.github.actions.icon"="check-square"
10+
LABEL "com.github.actions.color"="blue"
11+
12+
RUN apk update && apk add --no-cache ca-certificates
13+
14+
ADD https://github.com/atlassian/gajira/raw/master/bin/gagas .
15+
ADD . .
16+
RUN npm i
17+
RUN chmod +x /entrypoint.sh
18+
RUN chmod +x /gagas
19+
20+
ENTRYPOINT ["/entrypoint.sh"]

‎README.md

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Jira TODO
2+
Create issue for TODO comments
3+
4+
## Usage
5+
6+
Create Jira issue from TODO comments in pushed code.
7+
8+
Single-line comments in these formats:
9+
10+
```go
11+
// TODO: refactor this callback mess
12+
```
13+
```ruby
14+
# TODO: rewrite api client
15+
```
16+
17+
----
18+
## Action Spec:
19+
20+
### Environment variables
21+
- `GITHUB_TOKEN` - GitHub secret [token](https://developer.github.com/actions/creating-workflows/storing-secrets/#github-token-secret) is used to retrieve diffs
22+
23+
### Arguments
24+
25+
- `--project=<project key>` - Key of the project
26+
- `--issuetype=<issue type>` - Type of the issue to be created. Example: 'Task'

‎action.js

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
const _ = require('lodash')
2+
const fetch = require('node-fetch')
3+
const Jira = require('./common/net/Jira')
4+
const GitHub = require('./common/net/GitHub')
5+
6+
module.exports = class {
7+
constructor ({ githubEvent, argv, config, githubToken }) {
8+
this.Jira = new Jira({
9+
baseUrl: config.baseUrl,
10+
token: config.token,
11+
email: config.email,
12+
})
13+
14+
this.GitHub = new GitHub({
15+
token: githubToken
16+
})
17+
18+
this.config = config
19+
this.argv = argv
20+
this.githubEvent = githubEvent
21+
this.githubToken = githubToken
22+
}
23+
24+
async execute () {
25+
const { argv, githubEvent } = this
26+
const projectKey = argv.project
27+
const issuetypeName = argv.issuetype
28+
let tasks = []
29+
30+
if (githubEvent.commits && githubEvent.commits.length > 0) {
31+
tasks = _.flatten(await this.findTodoInCommits(githubEvent.repository, githubEvent.commits))
32+
}
33+
34+
if (tasks.length === 0) {
35+
console.log('no TODO found')
36+
37+
return
38+
}
39+
40+
// map custom fields
41+
const { projects } = await this.Jira.getCreateMeta({
42+
expand: 'projects.issuetypes.fields',
43+
projectKeys: projectKey,
44+
issuetypeNames: issuetypeName,
45+
})
46+
47+
if (projects.length === 0) {
48+
console.error(`project '${projectKey}' not found`)
49+
50+
return
51+
}
52+
53+
const [project] = projects
54+
55+
if (project.issuetypes.length === 0) {
56+
console.error(`issuetype '${issuetypeName}' not found`)
57+
58+
return
59+
}
60+
61+
const issues = tasks.map(async ({ summary, commitUrl }) => {
62+
let providedFields = [{
63+
key: 'project',
64+
value: {
65+
key: projectKey,
66+
},
67+
}, {
68+
key: 'issuetype',
69+
value: {
70+
name: issuetypeName,
71+
},
72+
}, {
73+
key: 'summary',
74+
value: summary,
75+
}]
76+
77+
if (!argv.description) {
78+
argv.description = `Created with GitHub commit ${commitUrl}`
79+
}
80+
81+
providedFields.push({
82+
key: 'description',
83+
value: argv.description,
84+
})
85+
86+
if (argv.fields) {
87+
providedFields = [...providedFields, ...this.transformFields(argv.fields)]
88+
}
89+
90+
const payload = providedFields.reduce((acc, field) => {
91+
acc.fields[field.key] = field.value
92+
93+
return acc
94+
}, {
95+
fields: {},
96+
})
97+
98+
return (await this.Jira.createIssue(payload)).key
99+
})
100+
101+
return { issues: await Promise.all(issues) }
102+
}
103+
104+
transformFields (fields) {
105+
return Object.keys(fields).map(fieldKey => ({
106+
key: fieldKey,
107+
value: fields[fieldKey],
108+
}))
109+
}
110+
111+
preprocessArgs () {
112+
_.templateSettings.interpolate = /{{([\s\S]+?)}}/g
113+
const descriptionTmpl = _.template(this.argv.description)
114+
115+
this.argv.description = descriptionTmpl({ event: this.githubEvent })
116+
}
117+
118+
async findTodoInCommits(repo, commits) {
119+
return Promise.all(commits.map(async (c) => {
120+
const res = await this.GitHub.getCommitDiff(repo.full_name, c.id)
121+
const rx = /^\+.*(?:\/\/|#)\s+TODO:(.*)$/gm
122+
return getMatches(res, rx, 1)
123+
.map(_.trim)
124+
.filter(Boolean)
125+
.map((s) => {
126+
return {
127+
commitUrl: c.url,
128+
summary: s,
129+
}
130+
})
131+
}))
132+
}
133+
}
134+
135+
function getMatches(string, regex, index) {
136+
index || (index = 1)
137+
var matches = []
138+
var match
139+
while (match = regex.exec(string)) {
140+
matches.push(match[index])
141+
}
142+
return matches
143+
}

‎common/net/GitHub.js

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
const { get } = require('lodash')
2+
3+
const serviceName = 'github'
4+
const { format } = require('url')
5+
const client = require('./client')(serviceName)
6+
7+
class GitHub {
8+
constructor ({token }) {
9+
this.baseUrl = 'https://api.github.com'
10+
this.token = token
11+
}
12+
13+
async getCommitDiff (repo, commitId) {
14+
return this.fetch('getCommitDiff',
15+
{ pathname: `/repos/${repo}/commits/${commitId}`},
16+
{
17+
headers: {
18+
Accept: 'application/vnd.github.v3.diff',
19+
}
20+
})
21+
}
22+
23+
async fetch (apiMethodName,
24+
{ host, pathname, query },
25+
{ method, body, headers = {} } = {}) {
26+
const url = format({
27+
host: host || this.baseUrl,
28+
pathname,
29+
query,
30+
})
31+
32+
if (!method) {
33+
method = 'GET'
34+
}
35+
36+
if (headers['Content-Type'] === undefined) {
37+
headers['Content-Type'] = 'application/json'
38+
}
39+
40+
if (headers.Authorization === undefined) {
41+
headers.Authorization = `token ${this.token}`
42+
}
43+
44+
// strong check for undefined
45+
// cause body variable can be 'false' boolean value
46+
if (body && headers['Content-Type'] === 'application/json') {
47+
body = JSON.stringify(body)
48+
}
49+
50+
const state = {
51+
req: {
52+
method,
53+
headers,
54+
body,
55+
url,
56+
},
57+
}
58+
59+
try {
60+
await client(state, `${serviceName}:${apiMethodName}`)
61+
} catch (error) {
62+
const fields = {
63+
originError: error,
64+
source: 'github',
65+
}
66+
67+
delete state.req.headers
68+
69+
throw Object.assign(
70+
new Error('GitHub API error'),
71+
state,
72+
fields,
73+
)
74+
}
75+
76+
return state.res.body
77+
}
78+
}
79+
80+
module.exports = GitHub

0 commit comments

Comments
 (0)
Failed to load comments.