add [Sonar] badges for various test metrics (#3571)
* feat: add SonarTests badges * chore: remove commented out line * refactor: update SonarTestsSummary to override transform
This commit is contained in:
@@ -80,12 +80,20 @@ module.exports = class SonarBase extends BaseJsonService {
|
||||
|
||||
transform({ json, sonarVersion }) {
|
||||
const useLegacyApi = isLegacyVersion({ sonarVersion })
|
||||
const rawValue = useLegacyApi
|
||||
? json[0].msr[0].val
|
||||
: json.component.measures[0].value
|
||||
const value = parseInt(rawValue)
|
||||
const metrics = {}
|
||||
|
||||
// Most values are numeric, but not all of them.
|
||||
return { metricValue: value || rawValue }
|
||||
if (useLegacyApi) {
|
||||
json[0].msr.forEach(measure => {
|
||||
// Most values are numeric, but not all of them.
|
||||
metrics[measure.key] = parseInt(measure.val) || measure.val
|
||||
})
|
||||
} else {
|
||||
json.component.measures.forEach(measure => {
|
||||
// Most values are numeric, but not all of them.
|
||||
metrics[measure.metric] = parseInt(measure.value) || measure.value
|
||||
})
|
||||
}
|
||||
|
||||
return metrics
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ module.exports = class SonarCoverage extends SonarBase {
|
||||
component,
|
||||
metricName: 'coverage',
|
||||
})
|
||||
const { metricValue: coverage } = this.transform({
|
||||
const { coverage } = this.transform({
|
||||
json,
|
||||
sonarVersion,
|
||||
})
|
||||
|
||||
@@ -63,7 +63,7 @@ module.exports = class SonarDocumentedApiDensity extends SonarBase {
|
||||
component,
|
||||
metricName: metric,
|
||||
})
|
||||
const { metricValue: density } = this.transform({ json, sonarVersion })
|
||||
return this.constructor.render({ density })
|
||||
const metrics = this.transform({ json, sonarVersion })
|
||||
return this.constructor.render({ density: metrics[metric] })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,9 @@ module.exports = class SonarFortifyRating extends SonarBase {
|
||||
metricName: 'fortify-security-rating',
|
||||
})
|
||||
|
||||
const { metricValue: rating } = this.transform({ json, sonarVersion })
|
||||
return this.constructor.render({ rating })
|
||||
const metrics = this.transform({ json, sonarVersion })
|
||||
return this.constructor.render({
|
||||
rating: metrics['fortify-security-rating'],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,16 +89,10 @@ const testsMetricNames = [
|
||||
'coverage_line_hits_data',
|
||||
'lines_to_cover',
|
||||
'new_lines_to_cover',
|
||||
'skipped_tests',
|
||||
'uncovered_conditions',
|
||||
'new_uncovered_conditions',
|
||||
'uncovered_lines',
|
||||
'new_uncovered_lines',
|
||||
'tests',
|
||||
'test_execution_time',
|
||||
'test_errors',
|
||||
'test_failures',
|
||||
'test_success_density',
|
||||
]
|
||||
const metricNames = [
|
||||
...complexityMetricNames,
|
||||
@@ -146,7 +140,10 @@ module.exports = class SonarGeneric extends SonarBase {
|
||||
metricName,
|
||||
})
|
||||
|
||||
const { metricValue } = this.transform({ json, sonarVersion })
|
||||
return this.constructor.render({ metricName, metricValue })
|
||||
const metrics = this.transform({ json, sonarVersion })
|
||||
return this.constructor.render({
|
||||
metricName,
|
||||
metricValue: metrics[metricName],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,10 @@ module.exports = class SonarQualityGate extends SonarBase {
|
||||
component,
|
||||
metricName: 'alert_status',
|
||||
})
|
||||
const { metricValue: qualityState } = this.transform({ json, sonarVersion })
|
||||
const { alert_status: qualityState } = this.transform({
|
||||
json,
|
||||
sonarVersion,
|
||||
})
|
||||
return this.constructor.render({ qualityState })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ module.exports = class SonarTechDebt extends SonarBase {
|
||||
// Special condition for backwards compatibility.
|
||||
metricName: 'sqale_debt_ratio',
|
||||
})
|
||||
const { metricValue: debt } = this.transform({ json, sonarVersion })
|
||||
const { sqale_debt_ratio: debt } = this.transform({ json, sonarVersion })
|
||||
return this.constructor.render({ debt, metric })
|
||||
}
|
||||
}
|
||||
|
||||
264
services/sonar/sonar-tests.service.js
Normal file
264
services/sonar/sonar-tests.service.js
Normal file
@@ -0,0 +1,264 @@
|
||||
'use strict'
|
||||
|
||||
const {
|
||||
testResultQueryParamSchema,
|
||||
renderTestResultBadge,
|
||||
documentation: testResultsDocumentation,
|
||||
} = require('../test-results')
|
||||
const { metric: metricCount } = require('../text-formatters')
|
||||
const SonarBase = require('./sonar-base')
|
||||
const {
|
||||
documentation,
|
||||
keywords,
|
||||
patternBase,
|
||||
queryParamSchema,
|
||||
getLabel,
|
||||
} = require('./sonar-helpers')
|
||||
|
||||
class SonarTestsSummary extends SonarBase {
|
||||
static get category() {
|
||||
return 'build'
|
||||
}
|
||||
|
||||
static get route() {
|
||||
return {
|
||||
base: 'sonar',
|
||||
pattern: `${patternBase}/tests`,
|
||||
queryParamSchema: queryParamSchema.concat(testResultQueryParamSchema),
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Sonar Tests',
|
||||
namedParams: {
|
||||
protocol: 'http',
|
||||
host: 'sonar.petalslink.com',
|
||||
component: 'org.ow2.petals:petals-se-ase',
|
||||
},
|
||||
queryParams: {
|
||||
sonarVersion: '4.2',
|
||||
compact_message: null,
|
||||
passed_label: 'passed',
|
||||
failed_label: 'failed',
|
||||
skipped_label: 'skipped',
|
||||
},
|
||||
staticPreview: this.render({
|
||||
passed: 5,
|
||||
failed: 1,
|
||||
skipped: 0,
|
||||
total: 6,
|
||||
isCompact: false,
|
||||
}),
|
||||
keywords,
|
||||
documentation: `
|
||||
${documentation}
|
||||
${testResultsDocumentation}
|
||||
`,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return {
|
||||
label: 'tests',
|
||||
}
|
||||
}
|
||||
|
||||
static render({
|
||||
passed,
|
||||
failed,
|
||||
skipped,
|
||||
total,
|
||||
passedLabel,
|
||||
failedLabel,
|
||||
skippedLabel,
|
||||
isCompact,
|
||||
}) {
|
||||
return renderTestResultBadge({
|
||||
passed,
|
||||
failed,
|
||||
skipped,
|
||||
total,
|
||||
passedLabel,
|
||||
failedLabel,
|
||||
skippedLabel,
|
||||
isCompact,
|
||||
})
|
||||
}
|
||||
|
||||
transform({ json, sonarVersion }) {
|
||||
const {
|
||||
tests: total,
|
||||
skipped_tests: skipped,
|
||||
test_failures: failed,
|
||||
} = super.transform({
|
||||
json,
|
||||
sonarVersion,
|
||||
})
|
||||
|
||||
return {
|
||||
total,
|
||||
passed: total - (skipped + failed),
|
||||
failed,
|
||||
skipped,
|
||||
}
|
||||
}
|
||||
|
||||
async handle(
|
||||
{ protocol, host, component },
|
||||
{
|
||||
sonarVersion,
|
||||
compact_message: compactMessage,
|
||||
passed_label: passedLabel,
|
||||
failed_label: failedLabel,
|
||||
skipped_label: skippedLabel,
|
||||
}
|
||||
) {
|
||||
const json = await this.fetch({
|
||||
sonarVersion,
|
||||
protocol,
|
||||
host,
|
||||
component,
|
||||
metricName: 'tests,test_failures,skipped_tests',
|
||||
})
|
||||
const { total, passed, failed, skipped } = this.transform({
|
||||
json,
|
||||
sonarVersion,
|
||||
})
|
||||
return this.constructor.render({
|
||||
passed,
|
||||
failed,
|
||||
skipped,
|
||||
total,
|
||||
isCompact: compactMessage !== undefined,
|
||||
passedLabel,
|
||||
failedLabel,
|
||||
skippedLabel,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class SonarTests extends SonarBase {
|
||||
static get category() {
|
||||
return 'build'
|
||||
}
|
||||
|
||||
static get route() {
|
||||
return {
|
||||
base: 'sonar',
|
||||
pattern: `${patternBase}/:metric(total_tests|skipped_tests|test_failures|test_errors|test_execution_time|test_success_density)`,
|
||||
queryParamSchema,
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Sonar Test Count',
|
||||
pattern: `${patternBase}/:metric(total_tests|skipped_tests|test_failures|test_errors)`,
|
||||
namedParams: {
|
||||
protocol: 'http',
|
||||
host: 'sonar.petalslink.com',
|
||||
component: 'org.ow2.petals:petals-log',
|
||||
metric: 'total_tests',
|
||||
},
|
||||
queryParams: {
|
||||
sonarVersion: '4.2',
|
||||
},
|
||||
staticPreview: this.render({
|
||||
metric: 'total_tests',
|
||||
value: 131,
|
||||
}),
|
||||
keywords,
|
||||
documentation,
|
||||
},
|
||||
{
|
||||
title: 'Sonar Test Execution Time',
|
||||
pattern: `${patternBase}/test_execution_time`,
|
||||
namedParams: {
|
||||
protocol: 'https',
|
||||
host: 'sonarcloud.io',
|
||||
component: 'swellaby:azure-pipelines-templates',
|
||||
},
|
||||
queryParams: {
|
||||
sonarVersion: '4.2',
|
||||
},
|
||||
staticPreview: this.render({
|
||||
metric: 'test_execution_time',
|
||||
value: 2,
|
||||
}),
|
||||
keywords,
|
||||
documentation,
|
||||
},
|
||||
{
|
||||
title: 'Sonar Test Success Rate',
|
||||
pattern: `${patternBase}/test_success_density`,
|
||||
namedParams: {
|
||||
protocol: 'https',
|
||||
host: 'sonarcloud.io',
|
||||
component: 'swellaby:azure-pipelines-templates',
|
||||
},
|
||||
queryParams: {
|
||||
sonarVersion: '4.2',
|
||||
},
|
||||
staticPreview: this.render({
|
||||
metric: 'test_success_density',
|
||||
value: 100,
|
||||
}),
|
||||
keywords,
|
||||
documentation,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return {
|
||||
label: 'tests',
|
||||
}
|
||||
}
|
||||
|
||||
static render({ value, metric }) {
|
||||
let color = 'blue'
|
||||
let label = getLabel({ metric })
|
||||
let message = metricCount(value)
|
||||
|
||||
if (metric === 'test_failures' || metric === 'test_errors') {
|
||||
color = value === 0 ? 'brightgreen' : 'red'
|
||||
} else if (metric === 'test_success_density') {
|
||||
color = value === 100 ? 'brightgreen' : 'red'
|
||||
label = 'tests'
|
||||
message = `${value}%`
|
||||
}
|
||||
|
||||
return {
|
||||
label,
|
||||
message,
|
||||
color,
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ protocol, host, component, metric }, { sonarVersion }) {
|
||||
const json = await this.fetch({
|
||||
sonarVersion,
|
||||
protocol,
|
||||
host,
|
||||
component,
|
||||
// We're using 'tests' as the metric key to provide our standard
|
||||
// formatted test badge (passed, failed, skipped) that exists for other
|
||||
// services. Therefore, we're exposing 'total_tests' to the user, and
|
||||
// need to map that to the 'tests' metric which sonar uses to represent the
|
||||
// total number of tests.
|
||||
// https://docs.sonarqube.org/latest/user-guide/metric-definitions/
|
||||
metricName: metric === 'total_tests' ? 'tests' : metric,
|
||||
})
|
||||
const metrics = this.transform({ json, sonarVersion })
|
||||
return this.constructor.render({
|
||||
value: metrics[metric === 'total_tests' ? 'tests' : metric],
|
||||
metric,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = [SonarTestsSummary, SonarTests]
|
||||
39
services/sonar/sonar-tests.spec.js
Normal file
39
services/sonar/sonar-tests.spec.js
Normal file
@@ -0,0 +1,39 @@
|
||||
'use strict'
|
||||
|
||||
const { test, given } = require('sazerac')
|
||||
const SonarTests = require('./sonar-tests.service')[1]
|
||||
|
||||
describe('SonarTests', function() {
|
||||
test(SonarTests.render, () => {
|
||||
given({ value: 0, metric: 'test_failures' }).expect({
|
||||
label: 'test failures',
|
||||
message: '0',
|
||||
color: 'brightgreen',
|
||||
})
|
||||
given({ value: 0, metric: 'test_errors' }).expect({
|
||||
label: 'test errors',
|
||||
message: '0',
|
||||
color: 'brightgreen',
|
||||
})
|
||||
given({ value: 2, metric: 'test_failures' }).expect({
|
||||
label: 'test failures',
|
||||
message: '2',
|
||||
color: 'red',
|
||||
})
|
||||
given({ value: 1, metric: 'test_errors' }).expect({
|
||||
label: 'test errors',
|
||||
message: '1',
|
||||
color: 'red',
|
||||
})
|
||||
given({ value: 100, metric: 'test_success_density' }).expect({
|
||||
label: 'tests',
|
||||
message: '100%',
|
||||
color: 'brightgreen',
|
||||
})
|
||||
given({ value: 93, metric: 'test_success_density' }).expect({
|
||||
label: 'tests',
|
||||
message: '93%',
|
||||
color: 'red',
|
||||
})
|
||||
})
|
||||
})
|
||||
159
services/sonar/sonar-tests.tester.js
Normal file
159
services/sonar/sonar-tests.tester.js
Normal file
@@ -0,0 +1,159 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('@hapi/joi')
|
||||
const { ServiceTester } = require('../tester')
|
||||
const t = (module.exports = new ServiceTester({
|
||||
id: 'SonarTests',
|
||||
title: 'SonarTests',
|
||||
pathPrefix: '/sonar',
|
||||
}))
|
||||
const {
|
||||
isDefaultTestTotals,
|
||||
isDefaultCompactTestTotals,
|
||||
isCustomTestTotals,
|
||||
isCustomCompactTestTotals,
|
||||
} = require('../test-validators')
|
||||
const { isIntegerPercentage, isMetric } = require('../test-validators')
|
||||
|
||||
t.create('Tests')
|
||||
.timeout(10000)
|
||||
.get('/https/sonarcloud.io/swellaby:azure-pipelines-templates/tests.json')
|
||||
.expectBadge({
|
||||
label: 'tests',
|
||||
message: isDefaultTestTotals,
|
||||
})
|
||||
|
||||
t.create('Tests (legacy API supported)')
|
||||
.timeout(10000)
|
||||
.get(
|
||||
'/http/sonar.petalslink.com/org.ow2.petals%3Apetals-se-ase/tests.json?sonarVersion=4.2'
|
||||
)
|
||||
.expectBadge({
|
||||
label: 'tests',
|
||||
message: isDefaultTestTotals,
|
||||
})
|
||||
|
||||
t.create('Tests with compact message')
|
||||
.timeout(10000)
|
||||
.get('/https/sonarcloud.io/swellaby:azure-pipelines-templates/tests.json', {
|
||||
qs: { compact_message: null },
|
||||
})
|
||||
.expectBadge({ label: 'tests', message: isDefaultCompactTestTotals })
|
||||
|
||||
t.create('Tests with custom labels')
|
||||
.timeout(10000)
|
||||
.get('/https/sonarcloud.io/swellaby:azure-pipelines-templates/tests.json', {
|
||||
qs: {
|
||||
passed_label: 'good',
|
||||
failed_label: 'bad',
|
||||
skipped_label: 'n/a',
|
||||
},
|
||||
})
|
||||
.expectBadge({ label: 'tests', message: isCustomTestTotals })
|
||||
|
||||
t.create('Tests with compact message and custom labels')
|
||||
.timeout(10000)
|
||||
.get('/https/sonarcloud.io/swellaby:azure-pipelines-templates/tests.json', {
|
||||
qs: {
|
||||
compact_message: null,
|
||||
passed_label: '💃',
|
||||
failed_label: '🤦♀️',
|
||||
skipped_label: '🤷',
|
||||
},
|
||||
})
|
||||
.expectBadge({
|
||||
label: 'tests',
|
||||
message: isCustomCompactTestTotals,
|
||||
})
|
||||
|
||||
t.create('Total Test Count')
|
||||
.timeout(10000)
|
||||
.get('/https/sonarcloud.io/swellaby:azdo-shellcheck/total_tests.json')
|
||||
.expectBadge({
|
||||
label: 'total tests',
|
||||
message: isMetric,
|
||||
})
|
||||
|
||||
t.create('Total Test Count (legacy API supported)')
|
||||
.timeout(10000)
|
||||
.get(
|
||||
'/http/sonar.petalslink.com/org.ow2.petals%3Apetals-se-ase/total_tests.json?sonarVersion=4.2'
|
||||
)
|
||||
.expectBadge({
|
||||
label: 'total tests',
|
||||
message: isMetric,
|
||||
})
|
||||
|
||||
t.create('Test Failures Count')
|
||||
.timeout(10000)
|
||||
.get('/https/sonarcloud.io/swellaby:azdo-shellcheck/test_failures.json')
|
||||
.expectBadge({
|
||||
label: 'test failures',
|
||||
message: Joi.alternatives(isMetric, 0),
|
||||
})
|
||||
|
||||
t.create('Test Failures Count (legacy API supported)')
|
||||
.timeout(10000)
|
||||
.get(
|
||||
'/http/sonar.petalslink.com/org.ow2.petals%3Apetals-se-ase/test_failures.json?sonarVersion=4.2'
|
||||
)
|
||||
.expectBadge({
|
||||
label: 'test failures',
|
||||
message: Joi.alternatives(isMetric, 0),
|
||||
})
|
||||
|
||||
t.create('Test Errors Count')
|
||||
.timeout(10000)
|
||||
.get('/https/sonarcloud.io/swellaby:azdo-shellcheck/test_errors.json')
|
||||
.expectBadge({
|
||||
label: 'test errors',
|
||||
message: Joi.alternatives(isMetric, 0),
|
||||
})
|
||||
|
||||
t.create('Test Errors Count (legacy API supported)')
|
||||
.timeout(10000)
|
||||
.get(
|
||||
'/http/sonar.petalslink.com/org.ow2.petals%3Apetals-se-ase/test_errors.json?sonarVersion=4.2'
|
||||
)
|
||||
.expectBadge({
|
||||
label: 'test errors',
|
||||
message: Joi.alternatives(isMetric, 0),
|
||||
})
|
||||
|
||||
t.create('Skipped Tests Count')
|
||||
.timeout(10000)
|
||||
.get('/https/sonarcloud.io/swellaby:azdo-shellcheck/skipped_tests.json')
|
||||
.expectBadge({
|
||||
label: 'skipped tests',
|
||||
message: Joi.alternatives(isMetric, 0),
|
||||
})
|
||||
|
||||
t.create('Skipped Tests Count (legacy API supported)')
|
||||
.timeout(10000)
|
||||
.get(
|
||||
'/http/sonar.petalslink.com/org.ow2.petals%3Apetals-se-ase/skipped_tests.json?sonarVersion=4.2'
|
||||
)
|
||||
.expectBadge({
|
||||
label: 'skipped tests',
|
||||
message: Joi.alternatives(isMetric, 0),
|
||||
})
|
||||
|
||||
t.create('Test Success Rate')
|
||||
.timeout(10000)
|
||||
.get(
|
||||
'/https/sonarcloud.io/swellaby:azdo-shellcheck/test_success_density.json'
|
||||
)
|
||||
.expectBadge({
|
||||
label: 'tests',
|
||||
message: isIntegerPercentage,
|
||||
})
|
||||
|
||||
t.create('Test Success Rate (legacy API supported)')
|
||||
.timeout(10000)
|
||||
.get(
|
||||
'/http/sonar.petalslink.com/org.ow2.petals%3Apetals-se-ase/test_success_density.json?sonarVersion=4.2'
|
||||
)
|
||||
.expectBadge({
|
||||
label: 'tests',
|
||||
message: isIntegerPercentage,
|
||||
})
|
||||
@@ -6,7 +6,6 @@ const SonarBase = require('./sonar-base')
|
||||
const {
|
||||
getLabel,
|
||||
documentation,
|
||||
isLegacyVersion,
|
||||
keywords,
|
||||
patternBase,
|
||||
queryParamWithFormatSchema,
|
||||
@@ -149,26 +148,12 @@ module.exports = class SonarViolations extends SonarBase {
|
||||
}
|
||||
|
||||
transformViolations({ json, sonarVersion, metric, format }) {
|
||||
// We can use the standard transform function in all cases
|
||||
// except when the requested badge is the long format of violations
|
||||
const metrics = this.transform({ json, sonarVersion })
|
||||
if (metric !== 'violations' || format !== 'long') {
|
||||
const { metricValue: violations } = this.transform({ json, sonarVersion })
|
||||
return { violations }
|
||||
return { violations: metrics[metric] }
|
||||
}
|
||||
|
||||
const useLegacyApi = isLegacyVersion({ sonarVersion })
|
||||
const measures = useLegacyApi ? json[0].msr : json.component.measures
|
||||
const violations = {}
|
||||
|
||||
measures.forEach(measure => {
|
||||
if (useLegacyApi) {
|
||||
violations[measure.key] = measure.val
|
||||
} else {
|
||||
violations[measure.metric] = measure.value
|
||||
}
|
||||
})
|
||||
|
||||
return { violations }
|
||||
return { violations: metrics }
|
||||
}
|
||||
|
||||
async handle(
|
||||
|
||||
Reference in New Issue
Block a user