Skip to content

Commit

Permalink
Merge branch 'develop' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
Blackjacx committed Sep 29, 2020
2 parents 56c08ed + 89fed02 commit 1d1cccb
Show file tree
Hide file tree
Showing 507 changed files with 59,060 additions and 157,281 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Change Log

## [1.1.0] - 2020-09-29
* [#5](https://github.com/Blackjacx/backlog-notifier/pull/5): Multiple Notifier References - [@Blackjacx](https://github.com/blackjacx).
4 changes: 3 additions & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
Copyright (c) 2020 Stefan Herold <stefan.herold@gmail.com>
MIT License

Copyright (c) 2020 Stefan Herold

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
44 changes: 33 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,53 @@
# Backlog Notifier Github Action (JS)
# Backlog Notifier GitHub Action (JS)

Comment on tickets connected to PRs in a teams platform specific repository in the same account about the release of a new PR / feature.
This action automatically notifies ticket owners (product owner, product managers, people watching tickets, ...) about included features when it gets triggered, e.g. at release time. It takes away the need of asking or manual commenting on tickets when they get resolved.

## How does it work?

This action was born to get rid of manually inform all responsible people that a certain backlog ticket has been closed when the connected PR makes it into a release.

In our company we have separate repos for iOS, Android and web. Additionally, we have global repos to handle backlog and bug tickets respectively. For each bug or backlog ticket we create an issue in the platform-specific repo and reference the master-ticket. This action is able to detect these references and automatically comment on the master ticket, e.g. when a tag is created. It can also put the released version into the comment. This automatically informs all stakeholders in which version the feature gets released or the bug gets resolved. For us this is a huge time saver.

Of course this can also be used if you have a simpler setup than the one described above. If you develop e.g. your own iOS app, wouldn't it be practical to automatically post a comment on each issue/PR with the released app version the issue/PR made it in? Yes you can do that too :)

For PRs that resolve multiple issues this action is also capable of detecting each reference and comment on all of the connected tickets.

The only thing this action relies on is that you maintain a CHANGELOG.md file following the format described on [Keep A Changelog](https://keepachangelog.com/).

## Inputs

### `backlog-ticket-prefix`
#### `reference-repo-prefixes` • required

**Required** Prefix of the backlog issue identifier. The whole identifier must look like `BACKLOG-539` and has to be part of the PR description. Default `"BACKLOG"`.
Prefixes for issue numbers of reference repos. You can specify multiple comma-separated ones. The should be single words like BACKLOG or BUGLOG which could identify repos for backlog and bug tickets respectively. A complete identifier, as the action searches it in the PR description, should consist of this prefix and the issue number separated by a dash, e.g. `BACKLOG-539`. **Default:** `BACKLOG`.

### `backlog-repo-name`
#### `reference-repo-names` • required

**Required** The repo name that contains your backlog tickets. Must be under the same github account as the repo you use this action in. Default `"backlog"`.
Repository names that contain tickets to notify. This list must have the same number of elements as the repository prefixes list. Specify one repository name for each prefix. The repository must be located under the same GitHub account as the repository you use this action on. **Default:** `backlog`.

### `message`
#### `message` • required

**Required** The message thats posted in the backlog ticket. Use `#` as placeholder for the version number. It will be automatically replaced. Default `"This feature has just been released for iOS 🎉 Now it takes typically 2-3 days until the release is available in the App Store."`.
The message thats posted in the backlog ticket. Use `#` as placeholder for the version number. It will be automatically replaced. Default `"This feature has just been released for iOS 🎉 Now it takes typically 2-3 days until the release is available in the App Store."`.

## Example usage
## Example Usage

The example below defines two prefixes and two repo names respectively, which enables the action to find connected tickets in the repos named `backlog` and `bug`.

```
uses: blackjacx/backlog-notifier@master
with:
backlog-ticket-prefix: 'BACKLOG'
backlog-repo-name: 'backlog'
reference-repo-prefixes: 'BACKLOG, BUG'
reference-repo-names: 'backlog, bug'
message: 'Dang! This feature is released in version # 🎉'
```

## Testing

I didn't find a way to properly test the JS action implementation in `index.js` locally yet. That's why I created the repos [Backlog](https://github.com/Blackjacx/backlog) and [GHTest](https://github.com/Blackjacx/ghtest).

Backlog is just setup with some issues where the action can comment on and which can be referenced from PRs in different repos.

GHTest contains a [shell script](https://github.com/Blackjacx/ghtest/blob/develop/trigger-backlog-notifier.sh) that can trigger this action. It will essentially push a tag to GitHub, i.e. create a release which is the main use case where this action should run. After executing the script this [GHTest workflow](https://github.com/Blackjacx/ghtest/blob/develop/.github/workflows/backlog-notifier.yml) is used to run the action on GitHub. It is configured to detect ticket references for both repos, `Backlog` and `GHTest`.

## License

This software is available under the MIT license. See [LICENSE](LICENSE) for details.
12 changes: 6 additions & 6 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
name: 'Backlog Notifier'
description: 'Comment on tickets connected to PRs in a teams platform specific repository in the same account about the release of a new PR / feature.'
inputs:
backlog-ticket-prefix:
description: 'Prefix of the backlog issue identifier. The whole identifier must look like `BACKLOG-539` and has to be part of the PR description.'
reference-repo-prefixes:
description: 'Prefixes for issue numbers of reference repos. You can specify multiple comma-separated ones. The should be single words like BACKLOG or BUGLOG which could identify repos for backlog and bug tickets respectively. A complete identifier, as the action searches it in the PR description, should consist of this prefix and the issue number separated by a dash, e.g. `BACKLOG-539`.'
required: true
default: 'BACKLOG'
backlog-repo-name:
description: 'The repo name that contains your backlog tickets. Must be under the same github account as the repo you use this action in.'
reference-repo-names:
description: 'Repository names that contain tickets to notify. This list must have the same number of elements as the repository prefixes list. Specify one repository name for each prefix. The repository must be located under the same GitHub account as the repository you use this action on.'
required: true
default: 'backlog'
message:
description: 'The message thats posted in the backlog ticket. Use `#` as placeholder for the version number.'
message:
description: 'The message that is posted in the backlog ticket. Use `#` as placeholder for the version number.'
required: true
default: 'This feature has just been released for iOS 🎉 Now it takes typically 2-3 days until the release is available in the App Store.'
runs:
Expand Down
153 changes: 81 additions & 72 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,93 +3,102 @@ const github = require('@actions/github');
const parseChangelog = require('changelog-parser');

async function getIssue(owner, repo, id) {
const octokit = new github.GitHub(`${process.env.GITHUB_TOKEN}`);

const { data: issue } = await octokit.issues.get({
owner: `${owner}`,
repo: `${repo}`,
issue_number: `${id}`
});
return issue
const octokit = github.getOctokit(`${process.env.GITHUB_TOKEN}`)

const { data: issue } = await octokit.issues.get({
owner: `${owner}`,
repo: `${repo}`,
issue_number: `${id}`
});
return issue
}

async function createComment(owner, repo, id, message) {
const octokit = new github.GitHub(`${process.env.GITHUB_TOKEN}`);
async function createComment(owner, repo, id, message) {
const octokit = github.getOctokit(`${process.env.GITHUB_TOKEN}`)

console.log(`Create comment in repo ${owner}/${repo} for issue number #${id}`)
console.log(`➡️ Creating comment for issue ${owner}/${repo}/issues/${id}`)

const { data: comment } = await octokit.issues.createComment({
owner: `${owner}`,
repo: `${repo}`,
issue_number: `${id}`,
body: `${message}`
});
return comment
const { data: comment } = await octokit.issues.createComment({
owner: `${owner}`,
repo: `${repo}`,
issue_number: `${id}`,
body: `${message}`
});
return comment
}

async function run() {
// Get the JSON webhook payload for the event that triggered the workflow
const payload = github.context.payload;
// Get the JSON webhook payload for the event that triggered the workflow
const payload = github.context.payload;

const tag = `${payload.ref}`.split('/').pop();
const tagUrl = `${payload.repository.html_url}/releases/tag/${tag}`;
const owner = `${payload.repository.owner.login}`;
const repo = `${payload.repository.name}`;
const tag = `${payload.ref}`.split('/').pop();
const tagUrl = `${payload.repository.html_url}/releases/tag/${tag}`;
const owner = `${payload.repository.owner.login}`;
const repo = `${payload.repository.name}`;

const backlogRepo = core.getInput('backlog-repo-name');
const backlogTicketPrefix = core.getInput('backlog-ticket-prefix');
const message = core.getInput('message').replace('#', `[${tag}](${tagUrl})`);
const referenceRepoNames = Array.from(new Set(core.getInput('reference-repo-names').split(',').map(string => string.trim())))
const referenceRepoPrefixes = Array.from(new Set(core.getInput('reference-repo-prefixes').split(',').map(string => string.trim())))
const message = core.getInput('message').replace('#', `[${tag}](${tagUrl})`);

console.log(`tag: ${tag}`);
console.log(`tagUrl: ${tagUrl}`);
console.log(`owner: ${owner}`);
console.log(`backlogRepo: ${backlogRepo}`);
console.log(`backlogTicketPrefix: ${backlogTicketPrefix}`);
console.log(`message: ${message}`);
console.log(`The event payload: ${JSON.stringify(payload, undefined, 2)})`);
console.log(`➡️ tag: ${tag}`);
console.log(`➡️ tagUrl: ${tagUrl}`);
console.log(`➡️ owner: ${owner}`);
console.log(`➡️ referenceRepoNames: ${referenceRepoNames} (${referenceRepoNames.length})`);
console.log(`➡️ referenceRepoPrefixes: ${referenceRepoPrefixes} (${referenceRepoPrefixes.length})`);
console.log(`➡️ message: ${message}`);
console.log(`➡️ The event payload: ${JSON.stringify(payload, undefined, 2)})`);

if (!`${payload.ref}`.startsWith('refs/tags/'))
throw Error(`The trigger for this action was not a tag reference!`);
if (referenceRepoNames.length != referenceRepoPrefixes.length)
throw Error('🔴 Different count in arrays "reference-repo-names" and "reference-repo-prefixes" Please specify same length. Repo names and repo prefixed must match.');

const changelog = await parseChangelog('CHANGELOG.md')
console.log(changelog);
if (!`${payload.ref}`.startsWith('refs/tags/'))
throw Error('🔴 The trigger for this action was not a tag reference!');

const filteredChangelog = changelog.versions.filter(function(obj) {
const changelog = await parseChangelog('CHANGELOG.md')
console.log('➡️ Changelog:\n%O', changelog);

const filteredChangelog = changelog.versions.filter(function(obj) {
return obj.version === `${tag}`;
});
console.log(filteredChangelog[0].body);

prIds = filteredChangelog[0].body.replace(/\* \[#/gi, '').replace(/\]\(https.*/gi, '').split('\n');
uniquePrIds = Array.from(new Set(prIds));
console.log(uniquePrIds);

for (const id of uniquePrIds) {
const pr = await getIssue(owner, repo, id);
console.log(pr);

let re = new RegExp(`${backlogTicketPrefix}-[0-9]+`, 'g');
let match = pr.body.match(re);

if (match == null) {
console.log(`Backlog reference not found on PR ${pr.html_url}. Specify it using the pattern "${backlogTicketPrefix}-<number>"`);
continue;
} else {
backlogId = match[0].match(/[0-9]+/g)[0];
console.log(`Backlog ID: ${backlogId}`);
});
console.log(`➡️ Filtered Changelog:\n${filteredChangelog[0].body}`);

prIds = filteredChangelog[0].body.replace(/\* \[#/gi, '').replace(/\]\(https.*/gi, '').split('\n');
uniquePrIds = Array.from(new Set(prIds))
console.log('➡️ Unique PR IDs:\n%O', uniquePrIds)

for (const id of uniquePrIds) {
const pr = await getIssue(owner, repo, id)
console.log('➡️ Pull Request:\n%O', pr)

for (const [i, prefix] of referenceRepoPrefixes.entries()) {
const repoName = referenceRepoNames[i]

let expression = new RegExp(`${prefix}-[0-9]+`, 'g')
let matches = pr.body.match(expression)

if (matches == null) {
console.log(`🔴 No issue references found for "${prefix}" on PR "${pr.html_url}". Please specify them using the pattern "${prefix}-<number>"`)
continue
}

for (const match of matches) {
issueReferenceID = match.match(/[0-9]+/g)[0]
console.log(`➡️ Issue reference found: ${match}`)

const comment = await createComment(owner, repoName, issueReferenceID, message)
console.log('🟢 Comment created:\n%O', comment)
}
}
}

const comment = await createComment(owner, backlogRepo, backlogId, message);
console.log(`Comment created\n${comment}`);
}
}
}

try {
run().then(function (result) {
console.log('Done 🎉')
}).catch(function (err) {
try {
run().then(function (result) {
console.log('🟢 Done 🎉')
}).catch(function (err) {
// Whoops, something went wrong!
console.error(err);
console.error(`🔴 ${err}`);
})
} catch (error) {
core.setFailed(error.message)
}
} catch (error) {
core.setFailed(error.message)
}
1 change: 0 additions & 1 deletion node_modules/.bin/semver

This file was deleted.

1 change: 0 additions & 1 deletion node_modules/.bin/which

This file was deleted.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion node_modules/@actions/core/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions node_modules/@actions/core/lib/command.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions node_modules/@actions/core/lib/command.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 1d1cccb

Please sign in to comment.