dd-trace
Version:
Datadog APM tracing client for JavaScript
108 lines (90 loc) • 3.43 kB
JavaScript
const { readFileSync } = require('node:fs')
const { gzipSync } = require('node:zlib')
const getConfig = require('../../config')
const FormData = require('../../exporters/common/form-data')
const request = require('../../exporters/common/request')
const log = require('../../log')
const {
incrementCountMetric,
distributionMetric,
TELEMETRY_COVERAGE_UPLOAD,
TELEMETRY_COVERAGE_UPLOAD_MS,
TELEMETRY_COVERAGE_UPLOAD_ERRORS,
TELEMETRY_COVERAGE_UPLOAD_BYTES,
} = require('../telemetry')
const UPLOAD_TIMEOUT_MS = 30_000
/**
* Uploads a single coverage report to the Datadog CI intake.
* One file per request with field names 'coverage' and 'event'.
* @param {object} options - Upload options
* @param {string} options.filePath - Path to the coverage report file
* @param {string} options.format - Format of the coverage report (e.g., 'lcov', 'cobertura')
* @param {object} options.testEnvironmentMetadata - Test environment metadata containing git/CI tags
* @param {URL} options.url - The base URL for the coverage report upload
* @param {boolean} [options.isEvpProxy] - Whether to use EVP proxy for the upload
* @param {string} [options.evpProxyPrefix] - The EVP proxy prefix (e.g., '/evp_proxy/v4')
* @param {Function} callback - Callback function (err)
*/
function uploadCoverageReport (
{ filePath, format, testEnvironmentMetadata, url, isEvpProxy, evpProxyPrefix },
callback
) {
const apiKey = getConfig().apiKey
if (!apiKey && !isEvpProxy) {
return callback(new Error('DD_API_KEY is required for coverage report upload'))
}
let compressedCoverage
try {
const coverageContent = readFileSync(filePath)
compressedCoverage = gzipSync(coverageContent)
} catch (err) {
return callback(new Error(`Failed to read coverage report at ${filePath}: ${err.message}`))
}
// Build the event payload with format, type, and all tags from test environment metadata
const eventPayload = {
type: 'coverage_report',
format,
...testEnvironmentMetadata,
}
// Create multipart form
const form = new FormData()
form.append('coverage', compressedCoverage, {
filename: 'coverage.gz',
contentType: 'application/gzip',
})
form.append('event', JSON.stringify(eventPayload), {
filename: 'event.json',
contentType: 'application/json',
})
const options = {
method: 'POST',
headers: {
...form.getHeaders(),
},
timeout: UPLOAD_TIMEOUT_MS,
url,
}
if (isEvpProxy) {
options.path = `${evpProxyPrefix}/api/v2/cicovreprt`
options.headers['X-Datadog-EVP-Subdomain'] = 'ci-intake'
} else {
options.path = '/api/v2/cicovreprt'
options.headers['dd-api-key'] = apiKey
}
log.debug('Uploading coverage report %s to %s%s', filePath, url, options.path)
incrementCountMetric(TELEMETRY_COVERAGE_UPLOAD)
distributionMetric(TELEMETRY_COVERAGE_UPLOAD_BYTES, {}, compressedCoverage.length)
const startTime = Date.now()
request(form, options, (err, res, statusCode) => {
distributionMetric(TELEMETRY_COVERAGE_UPLOAD_MS, {}, Date.now() - startTime)
if (err) {
incrementCountMetric(TELEMETRY_COVERAGE_UPLOAD_ERRORS, { statusCode })
log.error('Error uploading coverage report: %s', err.message)
return callback(err)
}
log.debug('Coverage report uploaded successfully (status: %d)', statusCode)
callback(null)
})
}
module.exports = { uploadCoverageReport }