Skip to content

Commit

Permalink
Add transitioning for PR events (#2)
Browse files Browse the repository at this point in the history
* add pull request functionality

* cleanup vars

* get stories from branch, title, or body for PR

* package
  • Loading branch information
kuritz authored Jun 29, 2020
1 parent 5d582d5 commit 48a3fe1
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 50 deletions.
69 changes: 58 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ A github action to update and transition the workflow state of clubhouse stories

## Usage

Currently the action supports updating stories listed in a github release.
Currently the action supports updating stories listed in a github release, or
pull request title.

### Releases

Expand All @@ -26,16 +27,16 @@ jobs:
update-clubhouse:
runs-on: ubuntu-latest
steps:
- uses: farmersdog/clubhouse-workflow-action@v1
with:
# Required. Auth token to use the clubhouse api for your workspace.
clubhouseToken: ${{ secrets.CLUBHOUSE_TOKEN }}
# Optional. The clubhouse workflow state released stories should be in.
# default: 'Completed'.
endStateName: Completed
# Optional. Whether to update story descriptions with a link to the release.
# default: false.
addReleaseInfo: false
- uses: farmersdog/clubhouse-workflow-action@v1
with:
# Required. Auth token to use the clubhouse api for your workspace.
clubhouseToken: ${{ secrets.CLUBHOUSE_TOKEN }}
# Optional. The clubhouse workflow state released stories should be in.
# default: 'Completed'.
endStateName: Completed
# Optional. Whether to update story descriptions with a link to the release.
# default: false.
addReleaseInfo: false
```
The only requirement for identifying stories in a release is that the id is prepended with `ch`. Any surrounding brackets, or no brackets, will all work.
Expand All @@ -60,3 +61,49 @@ If `addReleaseInfo: true` is set the action will update the story with a link ba
https://github.com/org/repo/releases/tag/v1.0.0
```
The action will only add `### Release Info` to a story if it's not already present. In some case a story is released more than once it will have a link to the first release.

### Pull Requests

Clubhouse [natively supports](https://help.clubhouse.io/hc/en-us/articles/208139833-Configuring-The-Clubhouse-GitHub-Event-Handlers) transitioning stories when a PR is opened or merged. This action can help with the in-between cases, like if a specific label is added to the PR.

```yaml
name: Move Stories to Testing
on:
pull_request:
types: ['labeled']
jobs:
update-clubhouse:
runs-on: ubuntu-latest
if: github.event.label.name == 'Ready for QA'
steps:
- uses: farmersdog/clubhouse-workflow-action@v1
with:
# Required. Auth token to use the clubhouse api for your workspace.
clubhouseToken: ${{ secrets.CLUBHOUSE_TOKEN }}
# Optional. The clubhouse workflow state released stories should be in.
# default: 'Completed'.
endStateName: In Testing
```

Or alternatively, you can run the action in response to a review request.

```yaml
name: Move Stories to Testing
on:
pull_request:
types: ['review_requested']
jobs:
update-clubhouse:
runs-on: ubuntu-latest
if: contains(github.event.pull_request.requested_teams.*.name, 'QA')
steps:
- uses: farmersdog/clubhouse-workflow-action@v1
with:
# Required. Auth token to use the clubhouse api for your workspace.
clubhouseToken: ${{ secrets.CLUBHOUSE_TOKEN }}
# Optional. The clubhouse workflow state released stories should be in.
# default: 'Completed'.
endStateName: In Testing
```
77 changes: 61 additions & 16 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1943,16 +1943,30 @@ const ch = __webpack_require__(519);

async function run() {
try {
const { body, html_url } = github.context.payload.release;
const addReleaseInfo = (core.getInput('addReleaseInfo') === 'true');
const releasedStories = await ch.releaseStories(
body,
core.getInput('endStateName'),
html_url,
addReleaseInfo
);
core.setOutput(releasedStories);
console.log(`Updated Stories: \n \n${releasedStories.join(' \n')}`);
const { payload, eventName } = github.context;
let updatedStories;
if (eventName === "release") {
const { body, html_url } = payload.release;
const addReleaseInfo = (core.getInput('addReleaseInfo') === 'true');
updatedStories = await ch.releaseStories(
body,
core.getInput('endStateName'),
html_url,
addReleaseInfo
);
} else if (eventName === "pull_request") {
const { title, body } = payload.pull_request;
const { ref } = payload.pull_request.head;
const content = `${title} ${body} ${ref}`;
updatedStories = await ch.transitionStories(
content,
core.getInput('endStateName')
);
} else {
throw new Error("Invalid event type");
}
core.setOutput(updatedStories);
console.log(`Updated Stories: \n \n${updatedStories.join(' \n')}`);
}
catch (error) {
core.setFailed(error.message);
Expand Down Expand Up @@ -12337,15 +12351,17 @@ const clubhouseToken = process.env.INPUT_CLUBHOUSETOKEN;
const client = Clubhouse.create(clubhouseToken);

/**
* Finds all clubhouse story IDs in body of release object.
* Finds all clubhouse story IDs in some string content.
*
* @param {string} releaseBody - The body field of a github release object.
* @param {string} content - content that may contain story IDs.
* @return {Array} - Clubhouse story IDs 1-7 digit strings.
*/

function extractStoryIds(releaseBody) {
function extractStoryIds(content) {
const regex = /(?<=ch)\d{1,7}/g;
return releaseBody.match(regex);
const all = content.match(regex);
const unique = [...new Set(all)];
return unique;
}

/**
Expand Down Expand Up @@ -12536,7 +12552,7 @@ async function releaseStories(
console.warn('No clubhouse stories were found in the release.');
return [];
}
const stories = await addDetailstoStories(storyIds, releaseUrl);
const stories = await addDetailstoStories(storyIds);
const storiesWithUpdatedDescriptions = updateDescriptionsMaybe(
stories,
releaseUrl,
Expand All @@ -12552,6 +12568,34 @@ async function releaseStories(
return updatedStoryNames;
}

/**
* Updates all clubhouse stories found in given content.
*
* @param {string} content - a string that might have clubhouse story IDs.
* @param {string} endStateName - Desired workflow state for stories.
* @return {Promise<Array>} - Names of the stories that were updated
*/

async function transitionStories(
content,
endStateName
) {
const storyIds = extractStoryIds(content);
if (storyIds.length === 0) {
console.warn('No clubhouse stories were found.');
return storyIds;
}
const stories = await addDetailstoStories(storyIds);
const workflows = await client.listWorkflows();
const storiesWithEndStateIds = addEndStateIds(
stories,
workflows,
endStateName
);
const updatedStoryNames = await updateStories(storiesWithEndStateIds);
return updatedStoryNames;
}

module.exports = {
client,
extractStoryIds,
Expand All @@ -12563,7 +12607,8 @@ module.exports = {
addEndStateIds,
updateStory,
updateStories,
releaseStories
releaseStories,
transitionStories
};


Expand Down
34 changes: 24 additions & 10 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,30 @@ const ch = require('./src/clubhouse');

async function run() {
try {
const { body, html_url } = github.context.payload.release;
const addReleaseInfo = (core.getInput('addReleaseInfo') === 'true');
const releasedStories = await ch.releaseStories(
body,
core.getInput('endStateName'),
html_url,
addReleaseInfo
);
core.setOutput(releasedStories);
console.log(`Updated Stories: \n \n${releasedStories.join(' \n')}`);
const { payload, eventName } = github.context;
let updatedStories;
if (eventName === "release") {
const { body, html_url } = payload.release;
const addReleaseInfo = (core.getInput('addReleaseInfo') === 'true');
updatedStories = await ch.releaseStories(
body,
core.getInput('endStateName'),
html_url,
addReleaseInfo
);
} else if (eventName === "pull_request") {
const { title, body } = payload.pull_request;
const { ref } = payload.pull_request.head;
const content = `${title} ${body} ${ref}`;
updatedStories = await ch.transitionStories(
content,
core.getInput('endStateName')
);
} else {
throw new Error("Invalid event type");
}
core.setOutput(updatedStories);
console.log(`Updated Stories: \n \n${updatedStories.join(' \n')}`);
}
catch (error) {
core.setFailed(error.message);
Expand Down
43 changes: 37 additions & 6 deletions src/clubhouse.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ const clubhouseToken = process.env.INPUT_CLUBHOUSETOKEN;
const client = Clubhouse.create(clubhouseToken);

/**
* Finds all clubhouse story IDs in body of release object.
* Finds all clubhouse story IDs in some string content.
*
* @param {string} releaseBody - The body field of a github release object.
* @param {string} content - content that may contain story IDs.
* @return {Array} - Clubhouse story IDs 1-7 digit strings.
*/

function extractStoryIds(releaseBody) {
function extractStoryIds(content) {
const regex = /(?<=ch)\d{1,7}/g;
return releaseBody.match(regex);
const all = content.match(regex);
const unique = [...new Set(all)];
return unique;
}

/**
Expand Down Expand Up @@ -203,7 +205,7 @@ async function releaseStories(
console.warn('No clubhouse stories were found in the release.');
return [];
}
const stories = await addDetailstoStories(storyIds, releaseUrl);
const stories = await addDetailstoStories(storyIds);
const storiesWithUpdatedDescriptions = updateDescriptionsMaybe(
stories,
releaseUrl,
Expand All @@ -219,6 +221,34 @@ async function releaseStories(
return updatedStoryNames;
}

/**
* Updates all clubhouse stories found in given content.
*
* @param {string} content - a string that might have clubhouse story IDs.
* @param {string} endStateName - Desired workflow state for stories.
* @return {Promise<Array>} - Names of the stories that were updated
*/

async function transitionStories(
content,
endStateName
) {
const storyIds = extractStoryIds(content);
if (storyIds.length === 0) {
console.warn('No clubhouse stories were found.');
return storyIds;
}
const stories = await addDetailstoStories(storyIds);
const workflows = await client.listWorkflows();
const storiesWithEndStateIds = addEndStateIds(
stories,
workflows,
endStateName
);
const updatedStoryNames = await updateStories(storiesWithEndStateIds);
return updatedStoryNames;
}

module.exports = {
client,
extractStoryIds,
Expand All @@ -230,5 +260,6 @@ module.exports = {
addEndStateIds,
updateStory,
updateStories,
releaseStories
releaseStories,
transitionStories
};
35 changes: 28 additions & 7 deletions test/clubhouse.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ other bugch015
`;
const release2 = '7895 [94536] (98453) #89';
const release3 = 'tchotchke ch-thing chi789';
const prTitle = 'Re-writing the app in another language [ch1919]';
const branch = 'user/ch2189/something-important-maybe';
const duplicates = 'Only one change [ch6754] ch6754 [ch6754]';
const releaseUrl = 'https://github.com/org/repo/releases/14';
const stories = [
{
Expand Down Expand Up @@ -110,27 +113,45 @@ other bugch015
describe('story id extraction from release body', function () {
const expectedIds0 = ['0002', '1', '12345', '987', '56789'];
const expectedIds1 = ['4287', '890', '8576', '3', '015'];
const expectedIds2 = null;
const expectedIds3 = null;
const expectedIds2 = [];
const expectedIds3 = [];
const expectedIdsPR = ['1919'];
const expectedIdsBranch = ['2189'];
const expectedIdsDups = ['6754'];

it('should find all story ids in well formatted release', function () {
const storyIds = ch.extractStoryIds(release0);
assert.deepEqual(storyIds, expectedIds0);
assert.deepStrictEqual(storyIds, expectedIds0);
});

it('should find all story ids in poorly formatted release', function () {
const storyIds = ch.extractStoryIds(release1);
assert.deepEqual(storyIds, expectedIds1);
assert.deepStrictEqual(storyIds, expectedIds1);
});

it('should not match plain number strings', function () {
const storyIds = ch.extractStoryIds(release2);
assert.strictEqual(storyIds, expectedIds2);
assert.deepStrictEqual(storyIds, expectedIds2);
});

it('should not match other strings beginning in "ch"', function () {
const storyIds = ch.extractStoryIds(release3);
assert.strictEqual(storyIds, expectedIds3);
assert.deepStrictEqual(storyIds, expectedIds3);
});

it('should find 1 story id in PR Title', function () {
const storyIds = ch.extractStoryIds(prTitle);
assert.deepStrictEqual(storyIds, expectedIdsPR);
});

it('should find 1 story id in branch name', function () {
const storyIds = ch.extractStoryIds(branch);
assert.deepStrictEqual(storyIds, expectedIdsBranch);
});

it('should find 1 story id from duplicates', function () {
const storyIds = ch.extractStoryIds(duplicates);
assert.deepStrictEqual(storyIds, expectedIdsDups);
});
});

Expand Down Expand Up @@ -220,7 +241,7 @@ https://github.com/org/repo/releases/14
releaseUrl,
false
);
assert.deepEqual(stories, newStories);
assert.deepStrictEqual(stories, newStories);
});
});

Expand Down

0 comments on commit 48a3fe1

Please sign in to comment.