Skip to content

Commit 4853d0b

Browse files
committed
chore(policy):SP-3822 mark policy threads as fixed when policy check pass
1 parent baf78dd commit 4853d0b

12 files changed

Lines changed: 102 additions & 36 deletions

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [1.5.0] - 2026-01-19
11+
### Added
12+
- Mark policy threads as fixed when policy checks pass (copyleft, undeclared, and dependency track)
13+
1014
## [1.4.0] - 2025-11-27
1115
### Changed
1216
- Updated security permission documentation
@@ -39,4 +43,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3943
[1.1.0]: https://github.com/scanoss/ado-code-scan/compare/v1.0.3...v1.1.0
4044
[1.2.0]: https://github.com/scanoss/ado-code-scan/compare/v1.1.0...v1.2.0
4145
[1.3.0]: https://github.com/scanoss/ado-code-scan/compare/v1.2.0...v1.3.0
42-
[1.4.0]: https://github.com/scanoss/ado-code-scan/compare/v1.3.0...v1.4.0
46+
[1.4.0]: https://github.com/scanoss/ado-code-scan/compare/v1.3.0...v1.4.0
47+
[1.5.0]: https://github.com/scanoss/ado-code-scan/compare/v1.4.0...v1.5.0

codescantask/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codescantask/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "azure-devops-integration",
3-
"version": "1.4.0",
3+
"version": "1.5.0",
44
"description": "",
55
"main": "index.js",
66
"scripts": {

codescantask/policies/copyleft-policy-check.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ export class CopyleftPolicyCheck extends PolicyCheck {
9898
const results = tl.execSync(EXECUTABLE, args);
9999

100100
if (results.code === 0) {
101-
await this.success('### :white_check_mark: Policy Pass \n #### Not copyleft Licenses were found', undefined);
101+
await this.success('### :white_check_mark: Policy Pass \n #### No copyleft licenses were found', undefined);
102+
await this.resolvePolicyThreads();
102103
return;
103104
}
104105

codescantask/policies/dep-track-policy-check.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ export class DepTrackPolicyCheck extends PolicyCheck {
256256
successMessage += this.getUploadConfigurationHelp();
257257
}
258258
await this.success(successMessage, undefined);
259+
await this.resolvePolicyThreads();
259260
return;
260261
}
261262
if (results.code === 1) {

codescantask/policies/policy-check.ts

Lines changed: 81 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@ export enum PR_STATUS {
3333
pending = 'pending',
3434
}
3535

36+
/**
37+
* @See: https://learn.microsoft.com/en-us/rest/api/azure/devops/git/pull-request-threads/update?view=azure-devops-rest-7.1#commentthreadstatus
38+
* */
39+
export enum THREAD_STATUS {
40+
active = 'active',
41+
pending = 'pending',
42+
fixed = 'fixed',
43+
wontFix = 'wontFix',
44+
closed = 'closed',
45+
byDesign = 'byDesign',
46+
unknown = 'unknown'
47+
}
48+
3649
export abstract class PolicyCheck {
3750
protected checkName: string;
3851
private readonly accessToken: string | undefined;
@@ -72,7 +85,6 @@ export abstract class PolicyCheck {
7285
if (text) {
7386
await this.addCommentToPR(`${this.checkName} Results`, text);
7487
}
75-
7688
}
7789

7890
protected async updatePRStatus(state: PR_STATUS, description: string){
@@ -113,23 +125,31 @@ export abstract class PolicyCheck {
113125
}
114126
}
115127

128+
protected async getPreviousThreads(): Promise<any[]> {
129+
if (this.buildReason && this.buildReason !== 'PullRequest') return [];
130+
try {
131+
const apiUrl = `${this.orgUrl}${this.project}/_apis/git/repositories/${this.repositoryId}/pullRequests/${this.pullRequestId}/threads?api-version=6.0`;
132+
const response = await axios.get(apiUrl, {
133+
headers: {
134+
'Content-Type': 'application/json',
135+
'Authorization': `Bearer ${this.accessToken}`
136+
}
137+
});
138+
return response.data.value || [];
139+
}
140+
catch (error: any) {
141+
tl.error(`Failed to get previous threads: ${error.message}`);
142+
return [];
143+
}
144+
}
145+
116146
/**
117147
* Deletes existing SCANOSS comments for this check type from the PR
118148
* Identifies comments by the SCANOSS marker and check name in the content
119149
*/
120150
private async deletePreviousComments(title: string):Promise<void> {
121-
if (this.buildReason && this.buildReason !== 'PullRequest') return;
122-
try {
123-
const apiUrl = `${this.orgUrl}${this.project}/_apis/git/repositories/${this.repositoryId}/pullRequests/${this.pullRequestId}/threads?api-version=6.0`;
124-
125-
const response = await axios.get(apiUrl, {
126-
headers: {
127-
'Content-Type': 'application/json',
128-
'Authorization': `Bearer ${this.accessToken}`
129-
}
130-
});
131-
132-
const threads = response.data.value || [];
151+
try{
152+
const threads = await this.getPreviousThreads();
133153
const scanossMarker = `SCANOSS - ${title}`;
134154
tl.debug(`Looking for threads with marker: ${scanossMarker}`);
135155
for (const thread of threads) {
@@ -158,7 +178,7 @@ export abstract class PolicyCheck {
158178
}
159179
}
160180

161-
protected async addCommentToPR(title: string, content: string, threadStatus: string = 'pending') {
181+
protected async addCommentToPR(title: string, content: string, threadStatus: THREAD_STATUS = THREAD_STATUS.pending) {
162182
if (this.buildReason && this.buildReason !== 'PullRequest') return;
163183
try {
164184
// Delete previous comments for this check type
@@ -188,15 +208,7 @@ export abstract class PolicyCheck {
188208
// Update the thread status using PATCH endpoint
189209
const threadId = response.data.id;
190210
if (threadId) {
191-
const patchUrl = `${this.orgUrl}${this.project}/_apis/git/repositories/${this.repositoryId}/pullRequests/${this.pullRequestId}/threads/${threadId}?api-version=7.1`;
192-
await axios.patch(patchUrl, {
193-
status: threadStatus
194-
}, {
195-
headers: {
196-
'Content-Type': 'application/json',
197-
'Authorization': `Bearer ${this.accessToken}`
198-
}
199-
});
211+
await this.updateThreadStatus(threadId, threadStatus);
200212
tl.debug(`Thread ${threadId} status updated to: ${threadStatus}`);
201213
}
202214
} catch (error: any) {
@@ -216,5 +228,50 @@ export abstract class PolicyCheck {
216228

217229
tl.command('artifact.upload', { artifactname: artifactName }, tempFilePath);
218230
}
219-
231+
232+
/**
233+
* Updates the status of a pull request thread.
234+
*
235+
* @param threadId - The ID of the thread to update
236+
* @param threadStatus - The new status to set (e.g., closed, active)
237+
* @see https://learn.microsoft.com/en-us/rest/api/azure/devops/git/pull-request-threads/update?view=azure-devops-rest-7.1
238+
*/
239+
protected async updateThreadStatus(threadId: string, threadStatus: THREAD_STATUS) {
240+
const patchUrl = `${this.orgUrl}${this.project}/_apis/git/repositories/${this.repositoryId}/pullRequests/${this.pullRequestId}/threads/${threadId}?api-version=7.1`;
241+
await axios.patch(patchUrl, {
242+
status: threadStatus
243+
}, {
244+
headers: {
245+
'Content-Type': 'application/json',
246+
'Authorization': `Bearer ${this.accessToken}`
247+
}
248+
});
249+
tl.debug(`Thread ${threadId} status updated to: ${threadStatus}`);
250+
}
251+
252+
/**
253+
* Resolve previous SCANOSS policy threads when the policy check passes.
254+
*
255+
* Searches through all PR threads for comments containing the SCANOSS marker
256+
* for this check type. When found, marks the thread as fixed to indicate the
257+
* policy violation has been resolved.
258+
*/
259+
protected async resolvePolicyThreads(): Promise<void> {
260+
const threads = await this.getPreviousThreads();
261+
const scanossMarker = `SCANOSS - ${this.checkName}`;
262+
for (const thread of threads) {
263+
if (thread.comments && thread.comments.length > 0) {
264+
for (const comment of thread.comments) {
265+
if (comment.content && comment.content.includes(scanossMarker)) {
266+
try {
267+
await this.updateThreadStatus(thread.id, THREAD_STATUS.fixed);
268+
} catch (error: any) {
269+
tl.warning(`Failed to resolve thread ${thread.id}: ${error.message}`);
270+
}
271+
break;
272+
}
273+
}
274+
}
275+
}
276+
}
220277
}

codescantask/policies/undeclared-policy-check.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
THE SOFTWARE.
2222
*/
2323

24-
import { PolicyCheck } from './policy-check';
24+
import {PolicyCheck} from './policy-check';
2525
import {DEBUG, EXECUTABLE, OUTPUT_FILEPATH, REPO_DIR, RUNTIME_CONTAINER, SCANOSS_SETTINGS} from '../app.input';
2626
import * as tl from 'azure-pipelines-task-lib';
2727
import * as fs from 'fs';
@@ -76,7 +76,9 @@ export class UndeclaredPolicyCheck extends PolicyCheck {
7676
const results = tl.execSync(EXECUTABLE, args);
7777

7878
if (results.code === 0) {
79-
await this.success('### :white_check_mark: Policy Pass \n #### Not undeclared components were found', undefined);
79+
tl.debug('No undeclared components were found');
80+
await this.success('### :white_check_mark: Policy Pass \n #### No undeclared components were found', undefined);
81+
await this.resolvePolicyThreads();
8082
return;
8183
}
8284

codescantask/task.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"author": "SCANOSS",
1010
"version": {
1111
"Major": 1,
12-
"Minor": 4,
12+
"Minor": 5,
1313
"Patch": 0
1414
},
1515
"instanceNameFormat": "SCANOSS Code Scan",

codescantask/tests/copyleftPolicySuite.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ describe('CopyleftPolicyCheck', () => {
184184
assert(summary !== undefined, 'Summary should not be undefined');
185185
// Add your assertions here
186186
assert.equal(sanitize(summary),sanitize(`### :white_check_mark: Policy Pass
187-
#### Not copyleft Licenses were found`));
187+
#### No copyleft licenses were found`));
188188
});
189189

190190
it('Copyleft policy explicit licenses', async function () {

codescantask/tests/undeclaredPolicySuite.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ describe('Undeclared Policy Check Suite', () => {
145145
assert(summary !== undefined, 'Summary should not be undefined');
146146

147147
assert.equal(sanitize(summary),sanitize(`### :white_check_mark: Policy Pass
148-
#### Not undeclared components were found`));
148+
#### No undeclared components were found`));
149149
});
150150

151151
it("should add '--debug' flag to build arguments when DEBUG is enabled", function() {

0 commit comments

Comments
 (0)