Skip to content

Commit

Permalink
🐛 QD-10984 Azure task fixes (#455)
Browse files Browse the repository at this point in the history
Fixes:
Quick-fixes push if both branch mode AND pr-mode are specified.
Coverage highlighting for any markdown (not only GitHub).
Ability to change args through UI

Co-authored-by: Andrei Iurko <[email protected]>
  • Loading branch information
AndreiIurko and Andrei Iurko authored Mar 3, 2025
1 parent daefe06 commit 61a97cc
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 81 deletions.
52 changes: 44 additions & 8 deletions common/__tests__/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,36 @@ import * as fs from 'fs'
import * as path from 'path'
import * as os from 'os'

test('test passed coverage output', () => {
test('test passed coverage output using diff', () => {
const result = getCoverageStats(
getCoverageFromSarif('__tests__/data/some.sarif.json')
getCoverageFromSarif('__tests__/data/some.sarif.json'),
true
)
expect(result).toEqual(passedCoverageFixture())
expect(result).toEqual(passedCoverageFixtureDiff())
})

test('test failed coverage output', () => {
test('test failed coverage output using diff', () => {
const result = getCoverageStats(
getCoverageFromSarif('__tests__/data/empty.sarif.json')
getCoverageFromSarif('__tests__/data/empty.sarif.json'),
true
)
expect(result).toEqual(failedCoverageFixture())
expect(result).toEqual(failedCoverageFixtureDiff())
})

test('test passed coverage output using spam', () => {
const result = getCoverageStats(
getCoverageFromSarif('__tests__/data/some.sarif.json'),
false
)
expect(result).toEqual(passedCoverageFixtureSpam())
})

test('test failed coverage output using spam', () => {
const result = getCoverageStats(
getCoverageFromSarif('__tests__/data/empty.sarif.json'),
false
)
expect(result).toEqual(failedCoverageFixtureSpam())
})

describe('getReportURL', () => {
Expand Down Expand Up @@ -90,7 +108,25 @@ describe('getReportURL', () => {
})
})

function passedCoverageFixture(): string {
function passedCoverageFixtureSpam(): string {
return `@@ Code coverage @@
<span style="background-color: #e6f4e6; color: green;">45% total lines covered</span>
124 lines analyzed, 56 lines covered
<span style="background-color: #e6f4e6; color: green;">33% fresh lines covered</span>
9 lines analyzed, 3 lines covered
# Calculated according to the filters of your coverage tool`
}

function failedCoverageFixtureSpam(): string {
return `@@ Code coverage @@
<span style="background-color: #ffe6e6; color: red;">0% total lines covered</span>
100 lines analyzed, 0 lines covered
<span style="background-color: #ffe6e6; color: red;">0% fresh lines covered</span>
100 lines analyzed, 0 lines covered
# Calculated according to the filters of your coverage tool`
}

function passedCoverageFixtureDiff(): string {
return `\`\`\`diff
@@ Code coverage @@
+ 45% total lines covered
Expand All @@ -101,7 +137,7 @@ function passedCoverageFixture(): string {
\`\`\``
}

function failedCoverageFixture(): string {
function failedCoverageFixtureDiff(): string {
return `\`\`\`diff
@@ Code coverage @@
- 0% total lines covered
Expand Down
30 changes: 16 additions & 14 deletions common/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,41 +99,43 @@ ${message}

function makeConclusion(
conclusion: string,
failedByThreshold: boolean
failedByThreshold: boolean,
useDiffBlock: boolean,
): string {
if (failedByThreshold) {
return `- ${conclusion}`
if (useDiffBlock) {
return failedByThreshold ? `- ${conclusion}` : `+ ${conclusion}`
} else {
return `+ ${conclusion}`
return failedByThreshold
? `<span style="background-color: #ffe6e6; color: red;">${conclusion}</span>`
: `<span style="background-color: #e6f4e6; color: green;">${conclusion}</span>`
}
}

export function getCoverageStats(c: Coverage): string {
export function getCoverageStats(c: Coverage, useDiffBlock: boolean): string {
if (c.totalLines === 0 && c.totalCoveredLines === 0) {
return ''
}

let stats = ''
if (c.totalLines !== 0) {
const conclusion = `${c.totalCoverage}% total lines covered`
stats += `${makeConclusion(conclusion, c.totalCoverage < c.totalCoverageThreshold)}
stats += `${makeConclusion(conclusion, c.totalCoverage < c.totalCoverageThreshold, useDiffBlock)}
${c.totalLines} lines analyzed, ${c.totalCoveredLines} lines covered`
}

if (c.freshLines !== 0) {
const conclusion = `${c.freshCoverage}% fresh lines covered`
stats += `
${makeConclusion(conclusion, c.freshCoverage < c.freshCoverageThreshold)}
${makeConclusion(conclusion, c.freshCoverage < c.freshCoverageThreshold, useDiffBlock)}
${c.freshLines} lines analyzed, ${c.freshCoveredLines} lines covered`
}

return wrapToDiffBlock(
[
`@@ Code coverage @@`,
`${stats}`,
`# Calculated according to the filters of your coverage tool`
].join('\n')
)
const coverageBlock = [
`@@ Code coverage @@`,
`${stats}`,
`# Calculated according to the filters of your coverage tool`
].join('\n')
return useDiffBlock ? wrapToDiffBlock(coverageBlock) : coverageBlock
}

export function getLicenseInfo(
Expand Down
29 changes: 14 additions & 15 deletions scan/dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -137016,36 +137016,35 @@ function wrapToDiffBlock(message) {
${message}
\`\`\``;
}
function makeConclusion(conclusion, failedByThreshold) {
if (failedByThreshold) {
return `- ${conclusion}`;
function makeConclusion(conclusion, failedByThreshold, useDiffBlock) {
if (useDiffBlock) {
return failedByThreshold ? `- ${conclusion}` : `+ ${conclusion}`;
} else {
return `+ ${conclusion}`;
return failedByThreshold ? `<span style="background-color: #ffe6e6; color: red;">${conclusion}</span>` : `<span style="background-color: #e6f4e6; color: green;">${conclusion}</span>`;
}
}
function getCoverageStats(c) {
function getCoverageStats(c, useDiffBlock) {
if (c.totalLines === 0 && c.totalCoveredLines === 0) {
return "";
}
let stats = "";
if (c.totalLines !== 0) {
const conclusion = `${c.totalCoverage}% total lines covered`;
stats += `${makeConclusion(conclusion, c.totalCoverage < c.totalCoverageThreshold)}
stats += `${makeConclusion(conclusion, c.totalCoverage < c.totalCoverageThreshold, useDiffBlock)}
${c.totalLines} lines analyzed, ${c.totalCoveredLines} lines covered`;
}
if (c.freshLines !== 0) {
const conclusion = `${c.freshCoverage}% fresh lines covered`;
stats += `
${makeConclusion(conclusion, c.freshCoverage < c.freshCoverageThreshold)}
${makeConclusion(conclusion, c.freshCoverage < c.freshCoverageThreshold, useDiffBlock)}
${c.freshLines} lines analyzed, ${c.freshCoveredLines} lines covered`;
}
return wrapToDiffBlock(
[
`@@ Code coverage @@`,
`${stats}`,
`# Calculated according to the filters of your coverage tool`
].join("\n")
);
const coverageBlock = [
`@@ Code coverage @@`,
`${stats}`,
`# Calculated according to the filters of your coverage tool`
].join("\n");
return useDiffBlock ? wrapToDiffBlock(coverageBlock) : coverageBlock;
}
function getLicenseInfo(resultsDir) {
let licensesInfo = "";
Expand Down Expand Up @@ -137558,7 +137557,7 @@ so that the action will upload the files as the job artifacts:
try {
const problems = (0, annotations_1.parseSarif)(`${resultsDir}/${qodana_12.QODANA_SARIF_NAME}`);
const reportUrl = (0, output_12.getReportURL)(resultsDir);
const coverageInfo = (0, output_12.getCoverageStats)((0, qodana_12.getCoverageFromSarif)(`${resultsDir}/${qodana_12.QODANA_SHORT_SARIF_NAME}`));
const coverageInfo = (0, output_12.getCoverageStats)((0, qodana_12.getCoverageFromSarif)(`${resultsDir}/${qodana_12.QODANA_SHORT_SARIF_NAME}`), true);
const licensesInfo = (0, output_12.getLicenseInfo)(resultsDir);
const problemsDescriptions = annotationsToProblemDescriptors(problems.annotations);
const toolName = (_a = problems.title.split("found by ")[1]) !== null && _a !== void 0 ? _a : output_12.QODANA_CHECK_NAME;
Expand Down
3 changes: 2 additions & 1 deletion scan/src/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ export async function publishOutput(
const problems = parseSarif(`${resultsDir}/${QODANA_SARIF_NAME}`)
const reportUrl = getReportURL(resultsDir)
const coverageInfo = getCoverageStats(
getCoverageFromSarif(`${resultsDir}/${QODANA_SHORT_SARIF_NAME}`)
getCoverageFromSarif(`${resultsDir}/${QODANA_SHORT_SARIF_NAME}`),
true
)

const licensesInfo: LicenseInfo = getLicenseInfo(resultsDir)
Expand Down
45 changes: 20 additions & 25 deletions vsts/QodanaScan/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16877,36 +16877,35 @@ function wrapToDiffBlock(message) {
${message}
\`\`\``;
}
function makeConclusion(conclusion, failedByThreshold) {
if (failedByThreshold) {
return `- ${conclusion}`;
function makeConclusion(conclusion, failedByThreshold, useDiffBlock) {
if (useDiffBlock) {
return failedByThreshold ? `- ${conclusion}` : `+ ${conclusion}`;
} else {
return `+ ${conclusion}`;
return failedByThreshold ? `<span style="background-color: #ffe6e6; color: red;">${conclusion}</span>` : `<span style="background-color: #e6f4e6; color: green;">${conclusion}</span>`;
}
}
function getCoverageStats(c) {
function getCoverageStats(c, useDiffBlock) {
if (c.totalLines === 0 && c.totalCoveredLines === 0) {
return "";
}
let stats = "";
if (c.totalLines !== 0) {
const conclusion = `${c.totalCoverage}% total lines covered`;
stats += `${makeConclusion(conclusion, c.totalCoverage < c.totalCoverageThreshold)}
stats += `${makeConclusion(conclusion, c.totalCoverage < c.totalCoverageThreshold, useDiffBlock)}
${c.totalLines} lines analyzed, ${c.totalCoveredLines} lines covered`;
}
if (c.freshLines !== 0) {
const conclusion = `${c.freshCoverage}% fresh lines covered`;
stats += `
${makeConclusion(conclusion, c.freshCoverage < c.freshCoverageThreshold)}
${makeConclusion(conclusion, c.freshCoverage < c.freshCoverageThreshold, useDiffBlock)}
${c.freshLines} lines analyzed, ${c.freshCoveredLines} lines covered`;
}
return wrapToDiffBlock(
[
`@@ Code coverage @@`,
`${stats}`,
`# Calculated according to the filters of your coverage tool`
].join("\n")
);
const coverageBlock = [
`@@ Code coverage @@`,
`${stats}`,
`# Calculated according to the filters of your coverage tool`
].join("\n");
return useDiffBlock ? wrapToDiffBlock(coverageBlock) : coverageBlock;
}
function getLicenseInfo(resultsDir) {
let licensesInfo = "";
Expand Down Expand Up @@ -79048,7 +79047,7 @@ so that the action will upload the files as the job artifacts:
try {
const problems = (0, utils_12.parseSarif)(`${resultsDir}/${qodana_12.QODANA_SARIF_NAME}`);
const reportUrl = (0, output_12.getReportURL)(resultsDir);
const coverageInfo = (0, output_12.getCoverageStats)((0, qodana_12.getCoverageFromSarif)(`${resultsDir}/${qodana_12.QODANA_SHORT_SARIF_NAME}`));
const coverageInfo = (0, output_12.getCoverageStats)((0, qodana_12.getCoverageFromSarif)(`${resultsDir}/${qodana_12.QODANA_SHORT_SARIF_NAME}`), false);
const licensesInfo = (0, output_12.getLicenseInfo)(resultsDir);
const problemsDescriptions = (_a = problems.problemDescriptions) !== null && _a !== void 0 ? _a : [];
const toolName = (_b = problems.title.split("found by ")[1]) !== null && _b !== void 0 ? _b : output_12.QODANA_CHECK_NAME;
Expand Down Expand Up @@ -79185,10 +79184,10 @@ var require_utils4 = __commonJS({
uploadSarif: tl2.getBoolInput("uploadSarif", false),
artifactName: tl2.getInput("artifactName", false) || "qodana-report",
useNightly: tl2.getBoolInput("useNightly", false),
prMode: tl2.getBoolInput("prMode", true),
prMode: tl2.getBoolInput("prMode", false),
postComment: tl2.getBoolInput("postPrComment", false),
pushFixes: tl2.getInput("pushFixes", false) || "none",
commitMessage: tl2.getInput("commitMessage", false) || "\u{1F916} Apply quick-fixes by Qodana",
commitMessage: tl2.getInput("commitMessage", false) || "\u{1F916} Apply quick-fixes by Qodana \n\n[skip ci]",
// Not used by the Azure task
additionalCacheKey: "",
primaryCacheKey: "",
Expand Down Expand Up @@ -79466,11 +79465,9 @@ ${comment_tag_pattern}`;
return;
}
if (mode === qodana_12.BRANCH) {
if (pullRequest) {
const commitToCherryPick = (yield gitOutput(["rev-parse", "HEAD"])).stdout.trim();
yield git(["checkout", currentBranch]);
yield git(["cherry-pick", commitToCherryPick]);
}
const commitToCherryPick = (yield gitOutput(["rev-parse", "HEAD"])).stdout.trim();
yield git(["checkout", currentBranch]);
yield git(["cherry-pick", commitToCherryPick]);
yield gitPush(currentBranch);
} else if (mode === qodana_12.PULL_REQUEST) {
const newBranch = `qodana/quick-fixes-${currentCommit.slice(0, 7)}`;
Expand All @@ -79488,9 +79485,7 @@ ${comment_tag_pattern}`;
const output = yield gitOutput(["push", "origin", branch], {
ignoreReturnCode: true
});
if (output.exitCode == 1) {
tl2.warning(`Branch ${branch} already exists. Push of quick-fixes was skipped.`);
} else if (output.exitCode !== 0) {
if (output.exitCode !== 0) {
tl2.warning(`Failed to push branch ${branch}: ${output.stderr}`);
}
});
Expand Down
26 changes: 25 additions & 1 deletion vsts/QodanaScan/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,33 @@
"name": "prMode",
"type": "boolean",
"label": "PR Mode",
"defaultValue": false,
"defaultValue": true,
"required": false,
"helpMarkDown": "Whether the PR analysis gets executed in the pull request mode."
},
{
"name": "postPrComment",
"type": "boolean",
"label": "Post PR comment",
"defaultValue": true,
"required": false,
"helpMarkDown": "Post a comment with the Qodana results summary to the pull request."
},
{
"name": "pushFixes",
"type": "string",
"label": "Push quick-fixes",
"defaultValue": "none",
"required": false,
"helpMarkDown": "Push Qodana fixes to the repository, can be `none`, `branch` to the current branch, or `pull-request`."
},
{
"name": "commitMessage",
"type": "string",
"label": "Commit Message",
"defaultValue": "\uD83E\uDD16 Apply quick-fixes by Qodana\n\n[skip ci]",
"required": false,
"helpMarkDown": "Message used when quick-fixes are pushed"
}
],
"execution": {
Expand Down
2 changes: 1 addition & 1 deletion vsts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ to
- or `branch`: push fixes to the original branch
3. Set the correct permissions for the job. Go to `Repositories` → `Manage repositories` → `Security`. Choose `Qodana for Azure Pipelines Build Service` user. Allow:
- `Contribute`
- `Bypass policies when pushing`. Without this, the analysis will be performed twice
- `Bypass policies when pushing` if they may fail the push of quick-fixes
- `Create branch` if you use `pull-request` value

Also, set `persistCredentials` property to `true`. This is needed for pushing changes to the repository
Expand Down
3 changes: 2 additions & 1 deletion vsts/src/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ export async function publishOutput(
const problems = parseSarif(`${resultsDir}/${QODANA_SARIF_NAME}`)
const reportUrl = getReportURL(resultsDir)
const coverageInfo = getCoverageStats(
getCoverageFromSarif(`${resultsDir}/${QODANA_SHORT_SARIF_NAME}`)
getCoverageFromSarif(`${resultsDir}/${QODANA_SHORT_SARIF_NAME}`),
false
)

const licensesInfo = getLicenseInfo(resultsDir)
Expand Down
Loading

0 comments on commit 61a97cc

Please sign in to comment.