Skip to content
Draft
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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ This action validates that pull requests and commits contain Azure DevOps work i
2. **Validates Commits** - Ensures each commit in a pull request has an Azure DevOps work item link (e.g. `AB#123`) in the commit message
3. **Automatically Links PRs to Work Items** - When a work item is referenced in a commit message, the action adds a GitHub Pull Request link to that work item in Azure DevOps
- 🎯 **This is the key differentiator**: By default, Azure DevOps only adds the Pull Request link to work items mentioned directly in the PR title or body, but this action also links work items found in commit messages!
4. **Visibility & Tracking** - Work item linkages are displayed as GitHub Actions notices and added to the job summary for easy visibility

## Action Output

The action provides visibility into linked work items through:

- **GitHub Actions Notices**: Work item links are displayed as notice annotations in the workflow run, making it easy to see which work items are linked
- Example: `Work item AB#12345 (from commit abc123d) linked to pull request #42`
- **Job Summary**: A summary of all linked work items is added to the workflow run's job summary page, providing a quick reference of work items associated with the PR
- Includes clickable links to commits and work items

## Usage

Expand Down
13 changes: 12 additions & 1 deletion __tests__/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,19 @@ const mockGetInput = jest.fn();
const mockSetFailed = jest.fn();
const mockInfo = jest.fn();
const mockError = jest.fn();
const mockNotice = jest.fn();
const mockSummary = {
addRaw: jest.fn().mockReturnThis(),
write: jest.fn().mockResolvedValue(undefined)
};

jest.unstable_mockModule('@actions/core', () => ({
getInput: mockGetInput,
setFailed: mockSetFailed,
info: mockInfo,
error: mockError
error: mockError,
notice: mockNotice,
summary: mockSummary
}));

// Mock @actions/github
Expand Down Expand Up @@ -64,6 +71,10 @@ describe('Azure DevOps Commit Validator', () => {
// Clear all mocks
jest.clearAllMocks();

// Reset summary mock
mockSummary.addRaw.mockClear().mockReturnThis();
mockSummary.write.mockClear().mockResolvedValue(undefined);

// Setup default mock implementations
mockGetInput.mockImplementation(name => {
const defaults = {
Expand Down
36 changes: 36 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,17 @@ async function checkCommitsForWorkItems(
process.env.GITHUB_SERVER_URL = process.env.GITHUB_SERVER_URL || 'https://github.com';

await linkWorkItem();

// Add notice annotation and job summary for visibility
const commitInfo = workItemToCommitMap.get(workItemId);
if (commitInfo) {
core.notice(`Work item AB#${workItemId} (from commit ${commitInfo.shortSha}) linked to pull request #${pullNumber}`, {
title: 'Work Item Linked'
});
core.summary.addRaw(`- Work item AB#${workItemId} (from commit [\`${commitInfo.shortSha}\`](${context.payload.repository?.html_url}/commit/${commitInfo.sha})) linked to pull request #${pullNumber}\n`);
}
}
await core.summary.write();
}

// Return the workItemToCommitMap and validation results for use in PR validation
Expand Down Expand Up @@ -481,10 +491,36 @@ async function checkPullRequestForWorkItems(
return invalidWorkItems;
}

// All work items valid - add notice and job summary for each
for (const workItem of uniqueWorkItems) {
const workItemNumber = workItem.substring(3); // Remove "AB#" prefix
core.notice(`Pull request linked to work item AB#${workItemNumber}`, {
title: 'Work Item Linked'
});
core.summary.addRaw(`- Pull request #${pullNumber} linked to work item AB#${workItemNumber}\n`);
}
await core.summary.write();

// All work items valid - return empty array
return [];
}

// Validation disabled - add notice and job summary for each work item
for (const workItem of uniqueWorkItems) {
const workItemNumber = workItem.substring(3); // Remove "AB#" prefix

// Add to the workItemToCommitMap if not already there
if (!workItemToCommitMap.has(workItemNumber)) {
workItemToCommitMap.set(workItemNumber, null); // null indicates it's from PR title/body
}

core.notice(`Pull request linked to work item AB#${workItemNumber}`, {
title: 'Work Item Linked'
});
core.summary.addRaw(`- Pull request #${pullNumber} linked to work item AB#${workItemNumber}\n`);
}
await core.summary.write();

// Validation disabled - return empty array
return [];
}
Expand Down