Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add transitioning for PR events #2

Merged
merged 5 commits into from
Jun 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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