Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add [Sonar] badges for various test metrics #3571

Merged
merged 10 commits into from
Jul 1, 2019
20 changes: 14 additions & 6 deletions services/sonar/sonar-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
2 changes: 1 addition & 1 deletion services/sonar/sonar-coverage.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ module.exports = class SonarCoverage extends SonarBase {
component,
metricName: 'coverage',
})
const { metricValue: coverage } = this.transform({
const { coverage } = this.transform({
json,
sonarVersion,
})
Expand Down
4 changes: 2 additions & 2 deletions services/sonar/sonar-documented-api-density.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -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] })
}
}
6 changes: 4 additions & 2 deletions services/sonar/sonar-fortify-rating.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
})
}
}
13 changes: 5 additions & 8 deletions services/sonar/sonar-generic.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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],
})
}
}
5 changes: 4 additions & 1 deletion services/sonar/sonar-quality-gate.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
}
}
2 changes: 1 addition & 1 deletion services/sonar/sonar-tech-debt.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 264 additions & 0 deletions services/sonar/sonar-tests.service.js
Original file line number Diff line number Diff line change
@@ -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,
})
}

transformTestResults({ json, sonarVersion }) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to make transform() and transformTestResults() as 2 seperate methods here?

Seems like it would be more natural to just override transform() and call the function in the parent class with super.transform()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea!

const {
tests: total,
skipped_tests: skipped,
test_failures: failed,
} = this.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.transformTestResults({
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]
Loading