Skip to content

Commit

Permalink
Introduce related cards feature
Browse files Browse the repository at this point in the history
  • Loading branch information
ukupat committed Apr 28, 2024
1 parent e3b9295 commit 3eb2270
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 27 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ github-include-new-card-command: false
# DEFAULT: false
github-require-keyword-prefix: false

# Ignores Trello URLs prefixed with "Related".
#
# Alternative approach when you don't want to use github-require-keyword-prefix but still want to link related cards for extra context.
#
# DEFAULT: false
github-enable-related-keyword-prefix: false

# Throws an error if no Trello card can be found in the PR.
#
# DEFAULT: false
Expand Down
3 changes: 3 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ inputs:
github-require-keyword-prefix:
description: When set to true, match only URLs prefixed with “Closes” etc. Just like https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword.
default: false
github-enable-related-keyword-prefix:
description: When set to true, ignore URLs prefixed with "Related".
default: false
github-require-trello-card:
description: Throw an error if no Trello cards can be found in the PR description.
default: false
Expand Down
37 changes: 25 additions & 12 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34634,22 +34634,34 @@ exports["default"] = isDraftPullRequest;
"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
const CARD_URL_REGEX = 'https://trello\\.com/c/(\\w+)(?:/[^\\s,]*)?';
const INCLUSION_KEYWORDS = ['close', 'closes', 'closed', 'fix', 'fixes', 'fixed', 'resolve', 'resolves', 'resolved'];
const EXCLUSION_KEYWORDS = ['related', 'relates', 'related to', 'relates to'];
function matchCardIds(conf, text) {
const keywords = ['close', 'closes', 'closed', 'fix', 'fixes', 'fixed', 'resolve', 'resolves', 'resolved'];
const keywordsRegExp = conf.githubRequireKeywordPrefix ? '(?:' + keywords.join('|') + ')\\s+' : '';
const urlRegExp = 'https://trello\\.com/c/(\\w+)(?:/[^\\s,]*)?';
const closesRegExp = `${keywordsRegExp}${urlRegExp}(?:\\s*,\\s*${urlRegExp})*`;
// Find all “Closes URL, URL…”
const matches = text?.match(new RegExp(closesRegExp, 'gi')) || [];
const matches = text?.match(buildRegExp(conf)) || [];
return extractUniqueCardIds(matches);
}
exports["default"] = matchCardIds;
function buildRegExp(conf) {
const inclusionRegex = buildInclusionRegex(conf);
const regex = conf.githubEnableRelatedKeywordPrefix ? buildRegexWithExclusion(inclusionRegex) : inclusionRegex;
return new RegExp(regex, 'gmi');
}
function buildInclusionRegex(conf) {
const keywordsRegExp = conf.githubRequireKeywordPrefix ? `(?:${INCLUSION_KEYWORDS.join('|')})\\s+` : '';
return `${keywordsRegExp}${CARD_URL_REGEX}(?:\\s*,\\s*${CARD_URL_REGEX})*`;
}
function buildRegexWithExclusion(inclusionRegex) {
return `^(?!.*\\b(${EXCLUSION_KEYWORDS.join('|')})\\b).*${inclusionRegex}`;
}
function extractUniqueCardIds(matches) {
return Array.from(new Set(matches.flatMap((match) => {
// Find URLs
const urlMatches = match.match(new RegExp(urlRegExp, 'g')) || [];
// Find cardId in the URL (only capture group in urlRegexp)
const cardIds = urlMatches.map((url) => url?.match(new RegExp(urlRegExp))?.[1] || '');
return cardIds;
// Find card URLs
const urlMatches = match.match(new RegExp(CARD_URL_REGEX, 'g')) || [];
// Extract card IDs from the URLs
return urlMatches.map((url) => url.match(new RegExp(CARD_URL_REGEX))?.[1] || '');
})));
}
exports["default"] = matchCardIds;


/***/ }),
Expand Down Expand Up @@ -34688,6 +34700,7 @@ const github_1 = __nccwpck_require__(5438);
const main_1 = __nccwpck_require__(399);
(0, main_1.run)((github_1.context.payload.pull_request || github_1.context.payload.issue), {
githubRequireKeywordPrefix: core.getBooleanInput('github-require-keyword-prefix'),
githubEnableRelatedKeywordPrefix: core.getBooleanInput('github-enable-related-keyword-prefix'),
githubRequireTrelloCard: core.getBooleanInput('github-require-trello-card'),
githubIncludePrComments: core.getBooleanInput('github-include-pr-comments'),
githubIncludePrBranchName: core.getBooleanInput('github-include-pr-branch-name'),
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@remato/trello-integration-action",
"version": "9.2.3",
"version": "9.3.0",
"license": "MIT",
"description": "GitHub Action to integrate Github pull requests with Trello cards",
"main": "dist/index.js",
Expand Down
35 changes: 35 additions & 0 deletions src/actions/getCardIds.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,41 @@ describe('Finding cards', () => {
expect(cardIds).toEqual(['card'])
})

describe('related cards', () => {
it('does not match related cards', async () => {
const cardIds = await getCardIds(
{ githubEnableRelatedKeywordPrefix: true },
{
...pr,
body: 'Related https://trello.com/c/card/title',
},
)
expect(cardIds).toEqual([])
})

it('does not match multiple related cards', async () => {
const cardIds = await getCardIds(
{ githubEnableRelatedKeywordPrefix: true },
{
...pr,
body: 'Relates to https://trello.com/c/card1/title https://trello.com/c/card2/title',
},
)
expect(cardIds).toEqual([])
})

it('matches related cards when feature is turned off', async () => {
const cardIds = await getCardIds(
{ githubEnableRelatedKeywordPrefix: false },
{
...pr,
body: 'Related https://trello.com/c/card/title',
},
)
expect(cardIds).toEqual(['card'])
})
})

describe('from branch name', () => {
it('finds basic card', async () => {
getBranchNameMock.mockResolvedValueOnce('1-card')
Expand Down
41 changes: 30 additions & 11 deletions src/actions/utils/matchCardIds.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,42 @@
import { Conf } from 'src/types'

const CARD_URL_REGEX = 'https://trello\\.com/c/(\\w+)(?:/[^\\s,]*)?'
const INCLUSION_KEYWORDS = ['close', 'closes', 'closed', 'fix', 'fixes', 'fixed', 'resolve', 'resolves', 'resolved']
const EXCLUSION_KEYWORDS = ['related', 'relates', 'related to', 'relates to']

export default function matchCardIds(conf: Conf, text?: string) {
const keywords = ['close', 'closes', 'closed', 'fix', 'fixes', 'fixed', 'resolve', 'resolves', 'resolved']
const keywordsRegExp = conf.githubRequireKeywordPrefix ? '(?:' + keywords.join('|') + ')\\s+' : ''
const urlRegExp = 'https://trello\\.com/c/(\\w+)(?:/[^\\s,]*)?'
const closesRegExp = `${keywordsRegExp}${urlRegExp}(?:\\s*,\\s*${urlRegExp})*`
const matches = text?.match(buildRegExp(conf)) || []

return extractUniqueCardIds(matches)
}

// Find all “Closes URL, URL…”
const matches = text?.match(new RegExp(closesRegExp, 'gi')) || []
function buildRegExp(conf: Conf): RegExp {
const inclusionRegex = buildInclusionRegex(conf)

const regex = conf.githubEnableRelatedKeywordPrefix ? buildRegexWithExclusion(inclusionRegex) : inclusionRegex

return new RegExp(regex, 'gmi')
}

function buildInclusionRegex(conf: Conf): string {
const keywordsRegExp = conf.githubRequireKeywordPrefix ? `(?:${INCLUSION_KEYWORDS.join('|')})\\s+` : ''

return `${keywordsRegExp}${CARD_URL_REGEX}(?:\\s*,\\s*${CARD_URL_REGEX})*`
}

function buildRegexWithExclusion(inclusionRegex: string): string {
return `^(?!.*\\b(${EXCLUSION_KEYWORDS.join('|')})\\b).*${inclusionRegex}`
}

function extractUniqueCardIds(matches: string[]): string[] {
return Array.from(
new Set(
matches.flatMap((match) => {
// Find URLs
const urlMatches = match.match(new RegExp(urlRegExp, 'g')) || []
// Find cardId in the URL (only capture group in urlRegexp)
const cardIds = urlMatches.map((url) => url?.match(new RegExp(urlRegExp))?.[1] || '')
// Find card URLs
const urlMatches = match.match(new RegExp(CARD_URL_REGEX, 'g')) || []

return cardIds
// Extract card IDs from the URLs
return urlMatches.map((url) => url.match(new RegExp(CARD_URL_REGEX))?.[1] || '')
}),
),
)
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { PR } from './types'

run((context.payload.pull_request || context.payload.issue) as PR, {
githubRequireKeywordPrefix: core.getBooleanInput('github-require-keyword-prefix'),
githubEnableRelatedKeywordPrefix: core.getBooleanInput('github-enable-related-keyword-prefix'),
githubRequireTrelloCard: core.getBooleanInput('github-require-trello-card'),
githubIncludePrComments: core.getBooleanInput('github-include-pr-comments'),
githubIncludePrBranchName: core.getBooleanInput('github-include-pr-branch-name'),
Expand Down
3 changes: 2 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { context } from '@actions/github'

export interface Conf {
githubRequireKeywordPrefix?: boolean
githubRequireTrelloCard?: boolean
githubEnableRelatedKeywordPrefix?: boolean
githubIncludePrComments?: boolean
githubIncludePrBranchName?: boolean
githubAllowMultipleCardsInPrBranchName?: boolean
githubIncludeNewCardCommand?: boolean
githubRequireKeywordPrefix?: boolean
githubUsersToTrelloUsers?: string
trelloListIdPrDraft?: string
trelloListIdPrOpen?: string
Expand Down

0 comments on commit 3eb2270

Please sign in to comment.