Skip to content

Commit d3e52ce

Browse files
committed
feat: enhance validation feedback by combining invalid work items from commits and PRs
1 parent d0c01fc commit d3e52ce

File tree

2 files changed

+71
-88
lines changed

2 files changed

+71
-88
lines changed

badges/coverage.svg

Lines changed: 1 addition & 1 deletion
Loading

src/index.js

Lines changed: 70 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,14 @@ export async function run() {
5050

5151
const octokit = github.getOctokit(githubToken);
5252

53-
// Store work item to commit mapping if we check commits
53+
// Store work item to commit mapping and validation results
5454
let workItemToCommitMap = new Map();
55+
let invalidWorkItemsFromCommits = [];
56+
let hasCommitFailures = false;
5557

5658
// Check commits
5759
if (checkCommits) {
58-
workItemToCommitMap = await checkCommitsForWorkItems(
60+
const commitResults = await checkCommitsForWorkItems(
5961
octokit,
6062
context,
6163
pullNumber,
@@ -67,11 +69,15 @@ export async function run() {
6769
azureDevopsToken,
6870
githubToken
6971
);
72+
workItemToCommitMap = commitResults.workItemToCommitMap;
73+
invalidWorkItemsFromCommits = commitResults.invalidWorkItems;
74+
hasCommitFailures = commitResults.hasCommitFailures;
7075
}
7176

7277
// Check pull request
78+
let invalidWorkItemsFromPR = [];
7379
if (checkPullRequest) {
74-
await checkPullRequestForWorkItems(
80+
invalidWorkItemsFromPR = await checkPullRequestForWorkItems(
7581
octokit,
7682
context,
7783
pullNumber,
@@ -82,6 +88,50 @@ export async function run() {
8288
workItemToCommitMap
8389
);
8490
}
91+
92+
// Combine all invalid work items and create ONE comment
93+
const allInvalidWorkItems = [...new Set([...invalidWorkItemsFromCommits, ...invalidWorkItemsFromPR])];
94+
95+
if (allInvalidWorkItems.length > 0 && commentOnFailure) {
96+
const { owner, repo } = context.repo;
97+
98+
// Build the work item list with commit info
99+
const workItemListItems = allInvalidWorkItems
100+
.map(id => {
101+
const commitInfo = workItemToCommitMap.get(id);
102+
if (commitInfo) {
103+
return `- \`AB#${id}\` (commit [\`${commitInfo.shortSha}\`](${context.payload.repository?.html_url}/commit/${commitInfo.sha}))`;
104+
}
105+
return `- \`AB#${id}\` (in PR title/body)`;
106+
})
107+
.join('\n');
108+
109+
const workItemList =
110+
allInvalidWorkItems.length > 1
111+
? `\n\n<details>\n<summary>View all ${allInvalidWorkItems.length} invalid work items</summary>\n\n${workItemListItems}\n</details>`
112+
: '';
113+
114+
// For single work item, include it inline; for multiple, use dropdown only
115+
const workItemReference = allInvalidWorkItems.length === 1 ? ` (\`AB#${allInvalidWorkItems[0]}\`)` : '';
116+
117+
await addOrUpdateComment(
118+
octokit,
119+
context,
120+
pullNumber,
121+
`${COMMENT_MARKERS.INVALID_WORK_ITEMS}\n:x: There ${allInvalidWorkItems.length === 1 ? 'is' : 'are'} ${allInvalidWorkItems.length} work item${allInvalidWorkItems.length === 1 ? '' : 's'}${workItemReference} in pull request #${pullNumber} that ${allInvalidWorkItems.length === 1 ? 'does' : 'do'} not exist in Azure DevOps. Please verify the work item${allInvalidWorkItems.length === 1 ? '' : 's'} and update the commit message${allInvalidWorkItems.length === 1 ? '' : 's'} or PR title/body.${workItemList}`,
122+
COMMENT_MARKERS.INVALID_WORK_ITEMS
123+
);
124+
}
125+
126+
// Fail if there were any invalid work items
127+
if (allInvalidWorkItems.length > 0) {
128+
core.error(
129+
`Invalid work item(s): There ${allInvalidWorkItems.length === 1 ? 'is' : 'are'} ${allInvalidWorkItems.length} work item${allInvalidWorkItems.length === 1 ? '' : 's'} that ${allInvalidWorkItems.length === 1 ? 'does' : 'do'} not exist in Azure DevOps`
130+
);
131+
core.setFailed(
132+
`There ${allInvalidWorkItems.length === 1 ? 'is' : 'are'} ${allInvalidWorkItems.length} work item${allInvalidWorkItems.length === 1 ? '' : 's'} that ${allInvalidWorkItems.length === 1 ? 'does' : 'do'} not exist in Azure DevOps`
133+
);
134+
}
85135
} catch (error) {
86136
core.setFailed(`Action failed with error: ${error}`);
87137
}
@@ -100,6 +150,7 @@ export async function run() {
100150
* @param {string} azureDevopsOrganization - Azure DevOps organization name
101151
* @param {string} azureDevopsToken - Azure DevOps PAT token
102152
* @param {string} githubToken - GitHub token
153+
* @returns {Object} Returns {workItemToCommitMap: Map, invalidWorkItems: Array, hasCommitFailures: boolean}
103154
*/
104155
async function checkCommitsForWorkItems(
105156
octokit,
@@ -200,7 +251,7 @@ async function checkCommitsForWorkItems(
200251
core.setFailed(
201252
`There ${invalidCommits.length === 1 ? 'is' : 'are'} ${invalidCommits.length} commit${invalidCommits.length === 1 ? '' : 's'} in pull request #${pullNumber} not linked to work items`
202253
);
203-
return workItemToCommitMap;
254+
return { workItemToCommitMap, invalidWorkItems: [], hasCommitFailures: true };
204255
}
205256

206257
// All commits are valid - check if there's an existing failure comment to update
@@ -246,49 +297,13 @@ async function checkCommitsForWorkItems(
246297
}
247298
}
248299

249-
// Handle invalid work items if any were found
300+
// If invalid work items found, return them (don't comment/fail here - let caller handle it)
250301
if (invalidWorkItems.length > 0) {
251302
const errorMessage = `Pull request contains ${invalidWorkItems.length === 1 ? 'an' : ''} invalid work item${invalidWorkItems.length === 1 ? '' : 's'}: ${invalidWorkItems.join(', ')}. ${invalidWorkItems.length === 1 ? 'This work item does' : 'These work items do'} not exist in Azure DevOps -- failing operation.`;
252303
console.log('');
253304
console.log('');
254305
console.log(errorMessage);
255-
core.error(
256-
`Invalid work item(s): There ${invalidWorkItems.length === 1 ? 'is' : 'are'} ${invalidWorkItems.length} work item${invalidWorkItems.length === 1 ? '' : 's'} that ${invalidWorkItems.length === 1 ? 'does' : 'do'} not exist in Azure DevOps`
257-
);
258-
259-
// Add comment to PR if comment-on-failure is true
260-
if (commentOnFailure) {
261-
const workItemListItems = invalidWorkItems
262-
.map(id => {
263-
const commitInfo = workItemToCommitMap.get(id);
264-
if (commitInfo) {
265-
return `- \`AB#${id}\` (commit [\`${commitInfo.shortSha}\`](${context.payload.repository?.html_url}/commit/${commitInfo.sha}))`;
266-
}
267-
return `- \`AB#${id}\` (in PR title/body)`;
268-
})
269-
.join('\n');
270-
271-
const workItemList =
272-
invalidWorkItems.length > 1
273-
? `\n\n<details>\n<summary>View all ${invalidWorkItems.length} invalid work items</summary>\n\n${workItemListItems}\n</details>`
274-
: '';
275-
276-
// For single work item, include it inline; for multiple, use dropdown only
277-
const workItemReference = invalidWorkItems.length === 1 ? ` (\`AB#${invalidWorkItems[0]}\`)` : '';
278-
279-
await addOrUpdateComment(
280-
octokit,
281-
context,
282-
pullNumber,
283-
`${COMMENT_MARKERS.INVALID_WORK_ITEMS}\n:x: There ${invalidWorkItems.length === 1 ? 'is' : 'are'} ${invalidWorkItems.length} work item${invalidWorkItems.length === 1 ? '' : 's'}${workItemReference} in pull request #${pullNumber} that ${invalidWorkItems.length === 1 ? 'does' : 'do'} not exist in Azure DevOps. Please verify the work item${invalidWorkItems.length === 1 ? '' : 's'} and update the commit message${invalidWorkItems.length === 1 ? '' : 's'} or PR title/body.${workItemList}`,
284-
COMMENT_MARKERS.INVALID_WORK_ITEMS
285-
);
286-
}
287-
288-
core.setFailed(
289-
`There ${invalidWorkItems.length === 1 ? 'is' : 'are'} ${invalidWorkItems.length} work item${invalidWorkItems.length === 1 ? '' : 's'} that ${invalidWorkItems.length === 1 ? 'does' : 'do'} not exist in Azure DevOps`
290-
);
291-
return workItemToCommitMap;
306+
return { workItemToCommitMap, invalidWorkItems, hasCommitFailures: false };
292307
}
293308

294309
// All work items are valid - check if there's an existing invalid work item comment to update
@@ -345,8 +360,8 @@ async function checkCommitsForWorkItems(
345360
}
346361
}
347362

348-
// Return the workItemToCommitMap for use in PR validation
349-
return workItemToCommitMap;
363+
// Return the workItemToCommitMap and validation results for use in PR validation
364+
return { workItemToCommitMap, invalidWorkItems: [], hasCommitFailures: false };
350365
}
351366

352367
/**
@@ -360,6 +375,7 @@ async function checkCommitsForWorkItems(
360375
* @param {string} azureDevopsOrganization - Azure DevOps organization name
361376
* @param {string} azureDevopsToken - Azure DevOps PAT token
362377
* @param {Map} workItemToCommitMap - Map of work item IDs to commit info from checkCommitsForWorkItems
378+
* @returns {Array} Returns array of invalid work item IDs found in PR title/body
363379
*/
364380
async function checkPullRequestForWorkItems(
365381
octokit,
@@ -458,59 +474,26 @@ async function checkPullRequestForWorkItems(
458474
}
459475
}
460476

461-
// Handle invalid work items if any were found
477+
// Return invalid work items if any were found (don't comment/fail here - let caller handle it)
462478
if (invalidWorkItems.length > 0) {
463479
const errorMessage = `Pull request contains ${invalidWorkItems.length === 1 ? 'an' : ''} invalid work item${invalidWorkItems.length === 1 ? '' : 's'}: ${invalidWorkItems.join(', ')}. ${invalidWorkItems.length === 1 ? 'This work item does' : 'These work items do'} not exist in Azure DevOps -- failing operation.`;
464480
console.log('');
465481
console.log('');
466482
console.log(errorMessage);
467-
core.error(
468-
`Invalid work item(s) in PR: There ${invalidWorkItems.length === 1 ? 'is' : 'are'} ${invalidWorkItems.length} work item${invalidWorkItems.length === 1 ? '' : 's'} in the PR that ${invalidWorkItems.length === 1 ? 'does' : 'do'} not exist in Azure DevOps`
469-
);
470-
471-
// Add comment to PR if comment-on-failure is true
472-
if (commentOnFailure) {
473-
const workItemListItems = invalidWorkItems
474-
.map(id => {
475-
const commitInfo = workItemToCommitMap.get(id);
476-
if (commitInfo) {
477-
return `- \`AB#${id}\` (commit [\`${commitInfo.shortSha}\`](${context.payload.repository?.html_url}/commit/${commitInfo.sha}))`;
478-
}
479-
return `- \`AB#${id}\` (in PR title/body)`;
480-
})
481-
.join('\n');
482-
483-
const workItemList =
484-
invalidWorkItems.length > 1
485-
? `\n\n<details>\n<summary>View all ${invalidWorkItems.length} invalid work items</summary>\n\n${workItemListItems}\n</details>`
486-
: '';
487-
488-
// For single work item, include it inline; for multiple, use dropdown only
489-
const workItemReference = invalidWorkItems.length === 1 ? ` (\`AB#${invalidWorkItems[0]}\`)` : '';
490-
491-
await addOrUpdateComment(
492-
octokit,
493-
context,
494-
pullNumber,
495-
`${COMMENT_MARKERS.INVALID_WORK_ITEMS}\n:x: There ${invalidWorkItems.length === 1 ? 'is' : 'are'} ${invalidWorkItems.length} work item${invalidWorkItems.length === 1 ? '' : 's'}${workItemReference} in pull request #${pullNumber} that ${invalidWorkItems.length === 1 ? 'does' : 'do'} not exist in Azure DevOps. Please verify the work item${invalidWorkItems.length === 1 ? '' : 's'} and update the commit message${invalidWorkItems.length === 1 ? '' : 's'} or PR title/body.${workItemList}`,
496-
COMMENT_MARKERS.INVALID_WORK_ITEMS
497-
);
498-
}
499-
500-
core.setFailed(
501-
`There ${invalidWorkItems.length === 1 ? 'is' : 'are'} ${invalidWorkItems.length} work item${invalidWorkItems.length === 1 ? '' : 's'} in the PR that ${invalidWorkItems.length === 1 ? 'does' : 'do'} not exist in Azure DevOps`
502-
);
503-
return;
483+
return invalidWorkItems;
504484
}
505485

506-
// Just log work items if validation is disabled
507-
for (const workItem of uniqueWorkItems) {
508-
const workItemNumber = workItem.substring(3); // Remove "AB#" prefix
509-
console.log(`PR title/body contains work item: ${workItemNumber}`);
510-
}
486+
// All work items valid - return empty array
487+
return [];
511488
}
489+
490+
// Validation disabled - return empty array
491+
return [];
512492
}
513493
}
494+
495+
// PR not linked to any work items - return empty array (this is handled separately)
496+
return [];
514497
}
515498

516499
/**

0 commit comments

Comments
 (0)