diff --git a/.travis.yml b/.travis.yml index f053805..e4d0452 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: node_js node_js: - - "5.7.1" + - "6.11.0" install: # Install packages. diff --git a/jobs/qg-filter/qg-filter.js b/jobs/qg-filter/qg-filter.js index 4dfa81c..e788b11 100644 --- a/jobs/qg-filter/qg-filter.js +++ b/jobs/qg-filter/qg-filter.js @@ -14,87 +14,90 @@ const util = require('util'); * "widgetTitle": "Quality Gate Failures" * } */ + +let sonarqubeVersionPromise; + module.exports = { - onRun: function (config, dependencies, job_callback) { - - fetchListOfProjects(config, dependencies) - .then(function (projects) { - return fetchProjectsMeasures(config, dependencies, projects); - }) - .then(decodeProjectsMeasures) - .then(function (projects) { - const millisecondsPerDay = 24 * 60 * 60 * 1000; - const timeHorizon = Date.now() - config.days_since_last_commitMax * millisecondsPerDay; - - return filterProjectsInError(projects - , timeHorizon); - }) - .then(function (projectsInError) { - - // prepare an array, by project - const projectsQA = Object.keys(projectsInError) - .sort() - .map(function (projectName) { - return { - name: projectName - , metricsError: projectsInError[projectName] - }; - }); - - job_callback(null, { - title: config.widgetTitle - , projects: projectsQA - }); - - - }) - .catch(function (err) { - job_callback(err.message); - }); - } + onInit: function(config, dependencies) { + sonarqubeVersionPromise = fetchSonarQubeVersion(config, dependencies); + }, + + onRun: function(config, dependencies, job_callback) { + + Promise.all( [ sonarqubeVersionPromise, fetchListOfProjects(config, dependencies) ] ) + .then(function([ sqVersion, projects ] ) { + return fetchProjectsMeasures(config, dependencies, sqVersion, projects); + }) + .then(decodeProjectsMeasures) + .then(function(projects) { + const millisecondsPerDay = 24 * 60 * 60 * 1000; + const timeHorizon = Date.now() - config.days_since_last_commitMax * millisecondsPerDay; + + return filterProjectsInError(projects, timeHorizon); + }) + .then(function(projectsInError) { + // prepare an array, by project + const projectsQA = Object.keys(projectsInError) + .sort() + .map(function(projectName) { + return { + name: projectName, + metricsError: projectsInError[projectName] + }; + }); + + job_callback(null, { + title: config.widgetTitle, + projects: projectsQA + }); + }) + .catch(function(err) { + job_callback(err.message); + }); + } }; function computeRequestOptionsForSQ(config, path) { - return { - url: config.globalAuth[config.credentials].rootURL + path, - headers: { - 'authorization': 'Basic ' + new Buffer( - config.globalAuth[config.credentials].username - + ':' - + config.globalAuth[config.credentials].password - ).toString('base64') - } - , timeout: config.interval * 3 - , rejectUnauthorized: false - }; + return { + url: config.globalAuth[config.credentials].rootURL + path, + headers: { + 'authorization': 'Basic ' + new Buffer( + config.globalAuth[config.credentials].username + + ':' + + config.globalAuth[config.credentials].password + ).toString('base64') + }, + timeout: config.interval * 3, + rejectUnauthorized: false + }; } module.exports.fetchListOfProjects = fetchListOfProjects; function fetchListOfProjects(config, dependencies) { - return new Promise( - function (resolve, reject) { - const filter = config.filterProjects ? util.format("&%s", config.filterProjects) : ''; - const path = '/api/projects/index?format=json' + filter; - const options = computeRequestOptionsForSQ(config, path); - dependencies.request(options, function (err, response, body) { - if (err) { - reject(err); - } else if (!response) { - reject(new Error('Bad response')); - } else if (response.statusCode !== 200) { - reject(new Error(util.format('Bad status %s', response.statusCode))); - } else { - try { - const bodyObj = JSON.parse(body); - resolve(bodyObj); - } catch (ex) { - reject(ex) - } - } - }); + return new Promise( + function(resolve, reject) { + const filter = config.filterProjects ? util.format("&%s", config.filterProjects) : ''; + const path = '/api/projects/index?format=json' + filter; + const options = computeRequestOptionsForSQ(config, path); + dependencies.request(options, function(err, response, body) { + if (err) { + reject(err); + } else if (!response) { + reject(new Error('Bad response')); + } else if (response.statusCode !== 200) { + reject(new Error(util.format('Bad status %s', response.statusCode))); + } else { + try { + const bodyObj = JSON.parse(body); + resolve(bodyObj); + } catch (ex) { + reject(ex) + } } - ); + }); + } + ); } /** @@ -102,129 +105,172 @@ function fetchListOfProjects(config, dependencies) { * @type {fetchProjectsMeasures} */ module.exports.fetchProjectsMeasures = fetchProjectsMeasures; -function fetchProjectsMeasures(config, dependencies, projects) { +function fetchProjectsMeasures(config, dependencies, sqVersion, projects) { + + let measureQuery; + let measureComponentField; + if( sqVersion.major > 6 + || sqVersion.major === 6 && sqVersion.minor >= 4 ) { + measureQuery = '/api/measures/component?componentKey=%s' + + '&metricKeys=quality_gate_details,alert_status,last_commit_date' + measureComponentField = 'component'; + } else{ + measureQuery = '/api/measures/component_tree?baseComponentKey=%s' + + '&metricKeys=quality_gate_details,alert_status,last_commit_date' + measureComponentField = 'baseComponent'; + } + + // synch with a promise and recursion + // with recursion, only 1 req at a time, diluting the load on SonarQube + return new Promise( + function(resolve, reject) { + + queryOneProject(resolve, reject, 0); + } + ); + + function queryOneProject(resolve, reject, projectIndex) { + if (projectIndex < projects.length) { + const path = util.format(measureQuery, projects[projectIndex].k); + const options = computeRequestOptionsForSQ(config, path); + dependencies.request(options, function(err, response, body) { + if (err) { + reject(err); + } else if (!response) { + reject(new Error('Bad response')); + } else if (response.statusCode !== 200) { + reject(new Error(util.format('Bad status %s', response.statusCode))); + } else { + try { + const bodyObj = JSON.parse(body); - // synch with a promise and recursion - // with recursion only 1 req at a time, dilluting the load on SonarQube - return new Promise( - function (resolve, reject) { + // enrich existing project object + projects[projectIndex].measures = bodyObj[measureComponentField].measures; - queryOneProject(resolve, reject, 0); - } - ); - - function queryOneProject(resolve, reject, projectIndex) { - if (projectIndex < projects.length) { - const path = util.format( - '/api/measures/component_tree?baseComponentKey=%s' + - '&metricKeys=quality_gate_details,alert_status,last_commit_date', - projects[projectIndex].k); - const options = computeRequestOptionsForSQ(config, path); - dependencies.request(options, function (err, response, body) { - if (err) { - reject(err) - } else if (!response) { - reject(new Error('Bad response')); - } else if (response.statusCode !== 200) { - reject(new Error(util.format('Bad status %s', response.statusCode))); - } else { - try { - const bodyObj = JSON.parse(body); - - // enrich existing project object - projects[projectIndex].measures = bodyObj.baseComponent.measures; - - // recursion - queryOneProject(resolve, reject, projectIndex + 1); - } catch (ex) { - reject(ex); - } - } - } - ); - } else { - resolve(projects); + // recursion + queryOneProject(resolve, reject, projectIndex + 1); + } catch (ex) { + reject(ex); + } } + }); + } else { + resolve(projects); } + } } module.exports.decodeProjectsMeasures = decodeProjectsMeasures; function decodeProjectsMeasures(projects) { - return new Promise( - function (resolve, reject) { - resolve(projects.map(function (projectObj) { - - var returned_project = { - key: projectObj.k - , name: projectObj.nm - }; - - // collect the values, - projectObj.measures.forEach(function (projectMsr) { - - const metricKey = projectMsr.metric; - var value; - switch (metricKey) { - case 'alert_status': - value = projectMsr.value; - break; - case 'quality_gate_details': - const data = JSON.parse(projectMsr.value); - // value is only the non OK measures - value = data.conditions.filter(function (condition) { - return condition.level !== 'OK'; - }); - break; - case 'last_commit_date': - value = parseInt(projectMsr.value, 10); - break; - default: - reject(new Error('Invalid measure key :' + metricKey)); - break; - } - - returned_project[metricKey] = value; - }); - - return returned_project; - - })); + return new Promise( + function(resolve, reject) { + resolve(projects.map(function(projectObj) { + + var returned_project = { + key: projectObj.k, + name: projectObj.nm + }; + + // collect the values, + projectObj.measures.forEach(function(projectMsr) { + + const metricKey = projectMsr.metric; + var value; + switch (metricKey) { + case 'alert_status': + value = projectMsr.value; + break; + case 'quality_gate_details': + const data = JSON.parse(projectMsr.value); + // value is only the non OK measures + value = data.conditions.filter(function(condition) { + return condition.level !== 'OK'; + }); + break; + case 'last_commit_date': + value = parseInt(projectMsr.value, 10); + break; + default: + reject(new Error('Invalid measure key :' + metricKey)); + break; + } + + returned_project[metricKey] = value; }); -} + return returned_project; + + })); + }); +} module.exports.filterProjectsInError = filterProjectsInError; function filterProjectsInError(projects, timeHorizon) { - return new Promise(function (resolve /*, reject*/) { - - // this will be returned - var projectsInError = {}; - - projects.forEach(function (project) { - - // only projet in ERROR - if (project.alert_status === 'ERROR' - // for less then XX days - && - ( ! timeHorizon || project.last_commit_date >= timeHorizon ) - ) { - // pick the measures in ERROR - projectsInError[project.name] = project.quality_gate_details.filter(function (oneMeasure) { - return oneMeasure.level === 'ERROR'; - }).map(function (oneFaultyMeasure) { - return { - // multiply and divide by ten preserves from bad rounding - count: Math.round(oneFaultyMeasure.actual * 10.0) / 10.0 - , metric: oneFaultyMeasure.metric - }; - - }); - } - }); + return new Promise(function(resolve /*, reject*/ ) { + + // this will be returned + var projectsInError = {}; + + projects.forEach(function(project) { + + // only projet in ERROR + if (project.alert_status === 'ERROR' + // for less then XX days + && + (!timeHorizon || project.last_commit_date >= timeHorizon) + ) { + // pick the measures in ERROR + projectsInError[project.name] = project.quality_gate_details.filter(function(oneMeasure) { + return oneMeasure.level === 'ERROR'; + }).map(function(oneFaultyMeasure) { + return { + // multiply and divide by ten preserves from bad rounding + count: Math.round(oneFaultyMeasure.actual * 10.0) / 10.0, + metric: oneFaultyMeasure.metric + }; + + }); + } + }); - resolve(projectsInError); + resolve(projectsInError); + }); +} + +module.exports.fetchSonarQubeVersion = fetchSonarQubeVersion +function fetchSonarQubeVersion(config, dependencies) { + return new Promise( + function(resolve, reject) { + const path = '/api/server/version'; + const options = computeRequestOptionsForSQ(config, path); + dependencies.request(options, function(err, response, body) { + if (err) { + reject(err); + } else if (!response) { + reject(new Error('Bad response')); + } else if (response.statusCode !== 200) { + reject(new Error(util.format('Bad status %s', response.statusCode))); + } else { + // attempt to decode the SQ version number, + const elts = body.match(/(\d+)\.(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:-(\w+))?/) + if (elts) { + resolve({ + full: elts[0], + major: Number.parseInt(elts[1]), + minor: Number.parseInt(elts[2]), + patch: elts[3] ? Number.parseInt(elts[3]) : null, + build: elts[4] ? Number.parseInt(elts[4]) : null, + qualifier: elts[5] + }); + } else { + resolve({ + full: body + }); + } } - ); + }); + } + ); } diff --git a/jobs/qg-filter/test/test.job.qg-filter.js b/jobs/qg-filter/test/test.job.qg-filter.js index a881411..9b6f2da 100644 --- a/jobs/qg-filter/test/test.job.qg-filter.js +++ b/jobs/qg-filter/test/test.job.qg-filter.js @@ -2,12 +2,12 @@ const assert = require('assert'); const util = require('util'); const qgFilter = require("../qg-filter.js"); -describe('qg-filter', function () { +describe('qg-filter', function() { var config; var dependencies; - beforeEach(function () { + beforeEach(function() { // mockup config config = { "interval": 10800000, @@ -26,157 +26,279 @@ describe('qg-filter', function () { bodyResponses: [], // mockup logger: { - log: function (text) { - }, - error: function (text) { - } + log: function(text) {}, + error: function(text) {} }, - request: function (options, request_callback) { - request_callback(0, { statusCode: 200 }, dependencies.bodyResponses.pop()); + request: function(options, request_callback) { + request_callback(0, { + statusCode: 200 + }, dependencies.bodyResponses.pop()); } }; }); - describe('fetchProjectsMeasures', function () { - it('should throw on error on bad status', function () { + describe('fetchProjectsMeasures', function() { + it('should throw on error on bad status', function() { - dependencies.request = function (options, request_callback) { - request_callback(0, { statusCode: 500 }, null); + dependencies.request = function(options, request_callback) { + request_callback(0, { + statusCode: 500 + }, null); }; - return qgFilter.fetchProjectsMeasures(config, dependencies, [{ k: 'foo', nm: 'bar' }]) - .then(function fulfilled (result) { - throw new Error('fetchProjectsMeasures was unexpectedly fulfilled. Result: ' + util.inspect(result)); - }, function rejected (error) { - assert(error instanceof Error); - }) + return qgFilter.fetchProjectsMeasures(config, dependencies, [{ + k: 'foo', + nm: 'bar' + }]) + .then(function fulfilled(result) { + throw new Error('fetchProjectsMeasures was unexpectedly fulfilled. Result: ' + util.inspect(result)); + }, function rejected(error) { + assert(error instanceof Error); + }) }); - it('should throw on error on bad JSON', function () { + it('should throw on error on bad JSON', function() { - dependencies.request = function (options, request_callback) { - request_callback(0, { statusCode: 500 }, null); + dependencies.request = function(options, request_callback) { + request_callback(0, { + statusCode: 500 + }, null); }; - return qgFilter.fetchProjectsMeasures(config, dependencies, [{ k: 'foo', nm: 'bar' }]) - .then(function fulfilled (result) { - throw new Error('fetchProjectsMeasures was unexpectedly fulfilled. Result: ' + util.inspect(result)); - }, function rejected (error) { - assert(error instanceof Error); - }) + return qgFilter.fetchProjectsMeasures(config, dependencies, [{ + k: 'foo', + nm: 'bar' + }]) + .then(function fulfilled(result) { + throw new Error('fetchProjectsMeasures was unexpectedly fulfilled. Result: ' + util.inspect(result)); + }, function rejected(error) { + assert(error instanceof Error); + }) }); }); - describe('decodeProjectsMeasures', function () { - it('should throw on unknown measure', function () { + describe('decodeProjectsMeasures', function() { + it('should throw on unknown measure', function() { return qgFilter.decodeProjectsMeasures( - [{ k: 'foo', nm: 'bar', measures: [{ metric: 'badmetric', value: 'notvalid' }] }]) - .then(function fulfilled (result) { - throw new Error('decodeProjectsMeasures was unexpectedly fulfilled. Result: ' + util.inspect(result)); - }, function rejected (error) { - assert(error instanceof Error); - assert.strictEqual(error.message, 'Invalid measure key :badmetric'); - }) + [{ + k: 'foo', + nm: 'bar', + measures: [{ + metric: 'badmetric', + value: 'notvalid' + }] + }]) + .then(function fulfilled(result) { + throw new Error('decodeProjectsMeasures was unexpectedly fulfilled. Result: ' + util.inspect(result)); + }, function rejected(error) { + assert(error instanceof Error); + assert.strictEqual(error.message, 'Invalid measure key :badmetric'); + }) }); }); - describe('end to end', function () { + describe('end to end', function() { + + describe('sonarQube < 6.4', function() { - beforeEach(function () { + beforeEach(function() { - // org.sonarsource.scm.cvs:sonar-scm-cvs-plugin - dependencies.bodyResponses.push( + // org.sonarsource.scm.cvs:sonar-scm-cvs-plugin + dependencies.bodyResponses.push( "{\n\n \"paging\": {\n \"pageIndex\": 1,\n \"pageSize\": 100,\n \"total\": 0\n },\n \"baseComponent\": {\n \"id\": \"1d9c31e0-1ab6-4be5-bbda-e4c4b1592c1c\",\n \"key\": \"org.sonarsource.scm.cvs:sonar-scm-cvs-plugin\",\n \"name\": \"SonarQube :: Plugins :: SCM :: CVS\",\n \"description\": \"CVS SCM Provider.\",\n \"qualifier\": \"TRK\",\n \"measures\": [\n {\n \"metric\": \"quality_gate_details\",\n \"value\": \"{\\n \\\"level\\\": \\\"ERROR\\\",\\n \\\"conditions\\\": [\\n {\\n \\\"metric\\\": \\\"new_sqale_debt_ratio\\\",\\n \\\"op\\\": \\\"GT\\\",\\n \\\"period\\\": 1,\\n \\\"warning\\\": \\\"\\\",\\n \\\"error\\\": \\\"5\\\",\\n \\\"actual\\\": \\\"0.0\\\",\\n \\\"level\\\": \\\"OK\\\"\\n },\\n {\\n \\\"metric\\\": \\\"reopened_issues\\\",\\n \\\"op\\\": \\\"GT\\\",\\n \\\"period\\\": 1,\\n \\\"warning\\\": \\\"0\\\",\\n \\\"error\\\": \\\"\\\",\\n \\\"actual\\\": \\\"0\\\",\\n \\\"level\\\": \\\"OK\\\"\\n },\\n {\\n \\\"metric\\\": \\\"open_issues\\\",\\n \\\"op\\\": \\\"GT\\\",\\n \\\"period\\\": 1,\\n \\\"warning\\\": \\\"0\\\",\\n \\\"error\\\": \\\"\\\",\\n \\\"actual\\\": \\\"9\\\",\\n \\\"level\\\": \\\"WARN\\\"\\n },\\n {\\n \\\"metric\\\": \\\"skipped_tests\\\",\\n \\\"op\\\": \\\"GT\\\",\\n \\\"period\\\": 1,\\n \\\"warning\\\": \\\"0\\\",\\n \\\"error\\\": \\\"\\\",\\n \\\"actual\\\": \\\"0\\\",\\n \\\"level\\\": \\\"OK\\\"\\n },\\n {\\n \\\"metric\\\": \\\"new_bugs\\\",\\n \\\"op\\\": \\\"GT\\\",\\n \\\"period\\\": 1,\\n \\\"warning\\\": \\\"\\\",\\n \\\"error\\\": \\\"1\\\",\\n \\\"actual\\\": \\\"1\\\",\\n \\\"level\\\": \\\"ERROR\\\"\\n },\\n {\\n \\\"metric\\\": \\\"new_vulnerabilities\\\",\\n \\\"op\\\": \\\"GT\\\",\\n \\\"period\\\": 1,\\n \\\"warning\\\": \\\"\\\",\\n \\\"error\\\": \\\"0\\\",\\n \\\"actual\\\": \\\"0\\\",\\n \\\"level\\\": \\\"OK\\\"\\n }\\n ]\\n}\"\n },\n {\n \"metric\": \"last_commit_date\",\n \"value\": \"1464600985000\",\n \"comment\": \"2016-05-30T09:36:25Z\"\n },\n {\n \"metric\": \"alert_status\",\n \"value\": \"ERROR\"\n }\n ]\n },\n \"components\": [ ]\n\n}" - ); - //sonar-packaging-maven-plugin - dependencies.bodyResponses.push( + ); + //sonar-packaging-maven-plugin + dependencies.bodyResponses.push( util.format( - "{\n\n \"paging\": {\n \"pageIndex\": 1,\n \"pageSize\": 100,\n \"total\": 0\n },\n \"baseComponent\": {\n \"id\": \"AVBLzFBb5VDW_Ub0IDJv\",\n \"key\": \"org.sonarsource.sonar-packaging-maven-plugin:sonar-packaging-maven-plugin\",\n \"name\": \"SonarQube :: Packaging Maven Plugin\",\n \"description\": \"Parent pom of SonarSource public projects\",\n \"qualifier\": \"TRK\",\n \"measures\": [\n {\n \"metric\": \"last_commit_date\",\n \"value\": \"%s\" },\n {\n \"metric\": \"quality_gate_details\",\n \"value\": \"{\\\"level\\\":\\\"ERROR\\\",\\\"conditions\\\":[{\\\"metric\\\":\\\"new_coverage\\\",\\\"op\\\":\\\"LT\\\",\\\"period\\\":1,\\\"warning\\\":\\\"\\\",\\\"error\\\":\\\"85\\\",\\\"actual\\\":\\\"0.0\\\",\\\"level\\\":\\\"ERROR\\\"},{\\\"metric\\\":\\\"new_sqale_debt_ratio\\\",\\\"op\\\":\\\"GT\\\",\\\"period\\\":1,\\\"warning\\\":\\\"\\\",\\\"error\\\":\\\"5\\\",\\\"actual\\\":\\\"2.380952380952381\\\",\\\"level\\\":\\\"OK\\\"},{\\\"metric\\\":\\\"reopened_issues\\\",\\\"op\\\":\\\"GT\\\",\\\"period\\\":1,\\\"warning\\\":\\\"0\\\",\\\"error\\\":\\\"\\\",\\\"actual\\\":\\\"0\\\",\\\"level\\\":\\\"OK\\\"},{\\\"metric\\\":\\\"open_issues\\\",\\\"op\\\":\\\"GT\\\",\\\"period\\\":1,\\\"warning\\\":\\\"0\\\",\\\"error\\\":\\\"\\\",\\\"actual\\\":\\\"0\\\",\\\"level\\\":\\\"OK\\\"},{\\\"metric\\\":\\\"skipped_tests\\\",\\\"op\\\":\\\"GT\\\",\\\"period\\\":1,\\\"warning\\\":\\\"0\\\",\\\"error\\\":\\\"\\\",\\\"actual\\\":\\\"0\\\",\\\"level\\\":\\\"OK\\\"},{\\\"metric\\\":\\\"new_bugs\\\",\\\"op\\\":\\\"GT\\\",\\\"period\\\":1,\\\"warning\\\":\\\"\\\",\\\"error\\\":\\\"0\\\",\\\"actual\\\":\\\"0\\\",\\\"level\\\":\\\"OK\\\"},{\\\"metric\\\":\\\"new_vulnerabilities\\\",\\\"op\\\":\\\"GT\\\",\\\"period\\\":1,\\\"warning\\\":\\\"\\\",\\\"error\\\":\\\"0\\\",\\\"actual\\\":\\\"0\\\",\\\"level\\\":\\\"OK\\\"}]}\"\n },\n {\n \"metric\": \"alert_status\",\n \"value\": \"ERROR\"\n }\n ]\n },\n \"components\": [ ]\n\n}", - Date.now() - 30 * 1000 // 30 seconds ago + "{\n\n \"paging\": {\n \"pageIndex\": 1,\n \"pageSize\": 100,\n \"total\": 0\n },\n \"baseComponent\": {\n \"id\": \"AVBLzFBb5VDW_Ub0IDJv\",\n \"key\": \"org.sonarsource.sonar-packaging-maven-plugin:sonar-packaging-maven-plugin\",\n \"name\": \"SonarQube :: Packaging Maven Plugin\",\n \"description\": \"Parent pom of SonarSource public projects\",\n \"qualifier\": \"TRK\",\n \"measures\": [\n {\n \"metric\": \"last_commit_date\",\n \"value\": \"%s\" },\n {\n \"metric\": \"quality_gate_details\",\n \"value\": \"{\\\"level\\\":\\\"ERROR\\\",\\\"conditions\\\":[{\\\"metric\\\":\\\"new_coverage\\\",\\\"op\\\":\\\"LT\\\",\\\"period\\\":1,\\\"warning\\\":\\\"\\\",\\\"error\\\":\\\"85\\\",\\\"actual\\\":\\\"0.0\\\",\\\"level\\\":\\\"ERROR\\\"},{\\\"metric\\\":\\\"new_sqale_debt_ratio\\\",\\\"op\\\":\\\"GT\\\",\\\"period\\\":1,\\\"warning\\\":\\\"\\\",\\\"error\\\":\\\"5\\\",\\\"actual\\\":\\\"2.380952380952381\\\",\\\"level\\\":\\\"OK\\\"},{\\\"metric\\\":\\\"reopened_issues\\\",\\\"op\\\":\\\"GT\\\",\\\"period\\\":1,\\\"warning\\\":\\\"0\\\",\\\"error\\\":\\\"\\\",\\\"actual\\\":\\\"0\\\",\\\"level\\\":\\\"OK\\\"},{\\\"metric\\\":\\\"open_issues\\\",\\\"op\\\":\\\"GT\\\",\\\"period\\\":1,\\\"warning\\\":\\\"0\\\",\\\"error\\\":\\\"\\\",\\\"actual\\\":\\\"0\\\",\\\"level\\\":\\\"OK\\\"},{\\\"metric\\\":\\\"skipped_tests\\\",\\\"op\\\":\\\"GT\\\",\\\"period\\\":1,\\\"warning\\\":\\\"0\\\",\\\"error\\\":\\\"\\\",\\\"actual\\\":\\\"0\\\",\\\"level\\\":\\\"OK\\\"},{\\\"metric\\\":\\\"new_bugs\\\",\\\"op\\\":\\\"GT\\\",\\\"period\\\":1,\\\"warning\\\":\\\"\\\",\\\"error\\\":\\\"0\\\",\\\"actual\\\":\\\"0\\\",\\\"level\\\":\\\"OK\\\"},{\\\"metric\\\":\\\"new_vulnerabilities\\\",\\\"op\\\":\\\"GT\\\",\\\"period\\\":1,\\\"warning\\\":\\\"\\\",\\\"error\\\":\\\"0\\\",\\\"actual\\\":\\\"0\\\",\\\"level\\\":\\\"OK\\\"}]}\"\n },\n {\n \"metric\": \"alert_status\",\n \"value\": \"ERROR\"\n }\n ]\n },\n \"components\": [ ]\n\n}", + Date.now() - 30 * 1000 // 30 seconds ago ) - ); - //burgr - dependencies.bodyResponses.push( + ); + //burgr + dependencies.bodyResponses.push( "{\n\n \"paging\": {\n \"pageIndex\": 1,\n \"pageSize\": 100,\n \"total\": 0\n },\n \"baseComponent\": {\n \"id\": \"AVKtK8s7wW7gmwRPbaVy\",\n \"key\": \"com.sonarsource.burgr:burgr\",\n \"name\": \"burgr\",\n \"description\": \"BUild Reports aGRegator\",\n \"qualifier\": \"TRK\",\n \"measures\": [\n {\n \"metric\": \"alert_status\",\n \"value\": \"OK\"\n },\n {\n \"metric\": \"last_commit_date\",\n \"value\": \"1467382075000\"\n },\n {\n \"metric\": \"quality_gate_details\",\n \"value\": \"{\\\"level\\\":\\\"OK\\\",\\\"conditions\\\":[{\\\"metric\\\":\\\"new_coverage\\\",\\\"op\\\":\\\"LT\\\",\\\"period\\\":1,\\\"warning\\\":\\\"\\\",\\\"error\\\":\\\"85\\\",\\\"actual\\\":\\\"\\\",\\\"level\\\":\\\"OK\\\"},{\\\"metric\\\":\\\"new_sqale_debt_ratio\\\",\\\"op\\\":\\\"GT\\\",\\\"period\\\":1,\\\"warning\\\":\\\"\\\",\\\"error\\\":\\\"5\\\",\\\"actual\\\":\\\"0.0\\\",\\\"level\\\":\\\"OK\\\"},{\\\"metric\\\":\\\"reopened_issues\\\",\\\"op\\\":\\\"GT\\\",\\\"period\\\":1,\\\"warning\\\":\\\"0\\\",\\\"error\\\":\\\"\\\",\\\"actual\\\":\\\"0\\\",\\\"level\\\":\\\"OK\\\"},{\\\"metric\\\":\\\"open_issues\\\",\\\"op\\\":\\\"GT\\\",\\\"period\\\":1,\\\"warning\\\":\\\"0\\\",\\\"error\\\":\\\"\\\",\\\"actual\\\":\\\"0\\\",\\\"level\\\":\\\"OK\\\"},{\\\"metric\\\":\\\"skipped_tests\\\",\\\"op\\\":\\\"GT\\\",\\\"period\\\":1,\\\"warning\\\":\\\"0\\\",\\\"error\\\":\\\"\\\",\\\"actual\\\":\\\"0\\\",\\\"level\\\":\\\"OK\\\"},{\\\"metric\\\":\\\"new_bugs\\\",\\\"op\\\":\\\"GT\\\",\\\"period\\\":1,\\\"warning\\\":\\\"\\\",\\\"error\\\":\\\"0\\\",\\\"actual\\\":\\\"0\\\",\\\"level\\\":\\\"OK\\\"},{\\\"metric\\\":\\\"new_vulnerabilities\\\",\\\"op\\\":\\\"GT\\\",\\\"period\\\":1,\\\"warning\\\":\\\"\\\",\\\"error\\\":\\\"0\\\",\\\"actual\\\":\\\"0\\\",\\\"level\\\":\\\"OK\\\"}]}\"\n }\n ]\n },\n \"components\": [ ]\n\n}" - ); - // list of projects - dependencies.bodyResponses.push( + ); + // list of projects + dependencies.bodyResponses.push( "[\n {\n \"id\": \"75263\",\n \"k\": \"com.sonarsource.burgr:burgr\",\n \"nm\": \"burgr\",\n \"sc\": \"PRJ\",\n \"qu\": \"TRK\"\n },\n {\n \"id\": \"65266\",\n \"k\": \"org.sonarsource.sonar-packaging-maven-plugin:sonar-packaging-maven-plugin\",\n \"nm\": \"SonarQube :: Packaging Maven Plugin\",\n \"sc\": \"PRJ\",\n \"qu\": \"TRK\"\n },\n {\n \"id\": \"35363\",\n \"k\": \"org.sonarsource.scm.cvs:sonar-scm-cvs-plugin\",\n \"nm\": \"SonarQube :: Plugins :: SCM :: CVS\",\n \"sc\": \"PRJ\",\n \"qu\": \"TRK\"\n }\n]" - ); - - }); - + ); + // /api/system/version + dependencies.bodyResponses.push( + "6.3.0.0" + ); + }); - it('should report 1 failed QG', function (done) { - qgFilter.onRun(config, dependencies, function job_callback (errMsg, data) { - assert.strictEqual(errMsg, null); + it('should report 2 failed QG', function(done) { - assert.strictEqual(data.title, config.widgetTitle); - assert(Array.isArray(data.projects)); - assert.strictEqual(data.projects.length, 2); + qgFilter.onInit(config, dependencies); + qgFilter.onRun(config, dependencies, function job_callback(errMsg, data) { + assert.strictEqual(errMsg, null); - assert.strictEqual(data.projects[0].name, 'SonarQube :: Packaging Maven Plugin'); - assert(Array.isArray(data.projects[0].metricsError)); - assert.strictEqual(data.projects[0].metricsError.length, 1); - assert.strictEqual(data.projects[0].metricsError[0].metric, 'new_coverage'); - assert.strictEqual(data.projects[0].metricsError[0].count, 0); + assert.strictEqual(data.title, config.widgetTitle); + assert(Array.isArray(data.projects)); + assert.strictEqual(data.projects.length, 2); - assert.strictEqual(data.projects[1].name, 'SonarQube :: Plugins :: SCM :: CVS'); - assert(Array.isArray(data.projects[1].metricsError)); - assert.strictEqual(data.projects[1].metricsError.length, 1); - assert.strictEqual(data.projects[1].metricsError[0].metric, 'new_bugs'); - assert.strictEqual(data.projects[1].metricsError[0].count, 1); + assert.strictEqual(data.projects[0].name, 'SonarQube :: Packaging Maven Plugin'); + assert(Array.isArray(data.projects[0].metricsError)); + assert.strictEqual(data.projects[0].metricsError.length, 1); + assert.strictEqual(data.projects[0].metricsError[0].metric, 'new_coverage'); + assert.strictEqual(data.projects[0].metricsError[0].count, 0); + assert.strictEqual(data.projects[1].name, 'SonarQube :: Plugins :: SCM :: CVS'); + assert(Array.isArray(data.projects[1].metricsError)); + assert.strictEqual(data.projects[1].metricsError.length, 1); + assert.strictEqual(data.projects[1].metricsError[0].metric, 'new_bugs'); + assert.strictEqual(data.projects[1].metricsError[0].count, 1); - done(); + done(); + }); }); - }); - it('should report one project since the time horizon', function (done) { + it('should report one project since the time horizon', function(done) { - config.days_since_last_commitMax = 5; + config.days_since_last_commitMax = 5; - qgFilter.onRun(config, dependencies, function job_callback (errMsg, data) { - assert.strictEqual(errMsg, null); + qgFilter.onInit(config, dependencies); + qgFilter.onRun(config, dependencies, function job_callback(errMsg, data) { + assert.strictEqual(errMsg, null); - assert.strictEqual(data.title, config.widgetTitle); - assert(Array.isArray(data.projects)); - assert.strictEqual(data.projects.length, 1); - assert.strictEqual(data.projects[0].name, 'SonarQube :: Packaging Maven Plugin'); + assert.strictEqual(data.title, config.widgetTitle); + assert(Array.isArray(data.projects)); + assert.strictEqual(data.projects.length, 1); + assert.strictEqual(data.projects[0].name, 'SonarQube :: Packaging Maven Plugin'); + + done(); + }); - done(); }); - }); + it('should handle request error', function(done) { - it('should handle request error', function (done) { + dependencies.request = function(options, request_callback) { + request_callback(0, { + statusCode: 500 + }, null); + }; - dependencies.request = function (options, request_callback) { - request_callback(0, { statusCode: 500 }, null); - }; + qgFilter.onInit(config, dependencies); + qgFilter.onRun(config, dependencies, function job_callback(errMsg, data) { - qgFilter.onRun(config, dependencies, function job_callback (errMsg, data) { + assert.strictEqual(errMsg, 'Bad status 500'); + assert.strictEqual(typeof data, 'undefined'); + done(); + }); + }); - assert.strictEqual(errMsg, 'Bad status 500'); - assert.strictEqual(typeof data, 'undefined'); - done(); + it('should handle bad data', function(done) { + dependencies.bodyResponses = [{ + "bad": "structure" + }]; + + qgFilter.onInit(config, dependencies); + qgFilter.onRun(config, dependencies, function job_callback(errMsg, data) { + + assert.strictEqual(typeof errMsg, 'string'); + assert.strictEqual(typeof data, 'undefined'); + done(); + }); }); }); - it('should handle bad data', function (done) { - dependencies.bodyResponses = [{ "bad": "structure" }]; - qgFilter.onRun(config, dependencies, function job_callback (errMsg, data) { + describe('sonarQube >= 6.4', function() { + it('should report no failed QG', function(done) { + + // query measures + dependencies.bodyResponses.push(JSON.stringify({ + "component": { + "id": "AV3X_wnRbZsMImgxuRpf", + "key": "atlasboard-sonarqube-package", + "name": "atlasboard-sonarqube-package", + "qualifier": "TRK", + "measures": [{ + "metric": "alert_status", + "value": "OK" + }, { + "metric": "quality_gate_details", + "value": "{\"level\":\"OK\",\"conditions\":[]}" + }, { + "metric": "last_commit_date", + "value": "1474401060000" + }] + } + })); + + // list projects + dependencies.bodyResponses.push(JSON.stringify([{ + "id": 1, + "k": "atlasboard-sonarqube-package", + "nm": "atlasboard-sonarqube-package", + "sc": "PRJ", + "qu": "TRK" + }])); + + // /api/system/version + dependencies.bodyResponses.push( + "6.4.0.0" + ); + + qgFilter.onInit(config, dependencies); + qgFilter.onRun(config, dependencies, function job_callback(errMsg, data) { + + assert.strictEqual(errMsg, null); + + assert.strictEqual(data.title, config.widgetTitle); + assert(Array.isArray(data.projects)); + assert.strictEqual(data.projects.length, 0); + + done(); + }); + }); - assert.strictEqual(typeof errMsg, 'string'); - assert.strictEqual(typeof data, 'undefined'); + it('should report 1 failed QG', function(done) { + + // query measures + dependencies.bodyResponses.push(JSON.stringify({ + "component": { + id: 'AV3X_wnRbZsMImgxuRpf', + key: "atlasboard-sonarqube-package", + name: "atlasboard-sonarqube-package", + qualifier: "TRK", + measures: [{ + metric: 'quality_gate_details', + value: '{\n "level": "ERROR",\n "conditions": [\n {\n "metric": "new_sqale_debt_ratio",\n "op": "GT",\n "period": 1,\n "warning": "",\n "error": "5",\n "actual": "0.0",\n "level": "OK"\n },\n {\n "metric": "reopened_issues",\n "op": "GT",\n "period": 1,\n "warning": "0",\n "error": "",\n "actual": "0",\n "level": "OK"\n },\n {\n "metric": "open_issues",\n "op": "GT",\n "period": 1,\n "warning": "0",\n "error": "",\n "actual": "9",\n "level": "WARN"\n },\n {\n "metric": "skipped_tests",\n "op": "GT",\n "period": 1,\n "warning": "0",\n "error": "",\n "actual": "0",\n "level": "OK"\n },\n {\n "metric": "new_bugs",\n "op": "GT",\n "period": 1,\n "warning": "",\n "error": "1",\n "actual": "1",\n "level": "ERROR"\n },\n {\n "metric": "new_vulnerabilities",\n "op": "GT",\n "period": 1,\n "warning": "",\n "error": "0",\n "actual": "0",\n "level": "OK"\n }\n ]\n}' + }, { + metric: 'last_commit_date', + value: '1464600985000', + comment: '2016-05-30T09:36:25Z' + }, + { + metric: 'alert_status', + value: 'ERROR' + } + ] + } + })); + + // list projects + dependencies.bodyResponses.push(JSON.stringify([{ + "id": 1, + "k": "atlasboard-sonarqube-package", + "nm": "atlasboard-sonarqube-package", + "sc": "PRJ", + "qu": "TRK" + }])); + + // /api/system/version + dependencies.bodyResponses.push( + "6.4.0.0" + ); done(); }); - }); }); }); diff --git a/package.json b/package.json index 6c58439..b5afb03 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "atlasboard-sonarqube-package", - "version": "1.0.1", + "version": "1.1.0", "description": "SonarQube package for Atlasboard", "repository": { "type": "git", "url": "git+https://github.com/SonarQubeCommunity/atlasboard-sonarqube-package.git" }, "engines": { - "node": ">=5.7.1" + "node": ">=6.11.2" }, "keywords": [ "atlasboard", @@ -19,15 +19,15 @@ "devDependencies": { "gulp": "^3.9.1", "gulp-istanbul": "^1.1.1", - "gulp-mocha": "^3.0.1", + "gulp-mocha": "^4.3.1", "istanbul": "^0.4.3", "jquery": "^3.1.0", - "jsdom": "^9.4.1", - "mocha": "2.3.3", - "sonarqube-scanner": "0.4.0" + "jsdom": "^11.1.0", + "mocha": "3.5.0", + "sonarqube-scanner": "2.0.1" }, "dependencies": { - "atlasboard": "~1.0.1" + "atlasboard": "~1.1.3" }, "scripts": { "test": "mocha --recursive jobs widgets", diff --git a/widgets/qg-filter/test/qg-filter.js b/widgets/qg-filter/test/qg-filter.js index c9d5335..abe1fbc 100644 --- a/widgets/qg-filter/test/qg-filter.js +++ b/widgets/qg-filter/test/qg-filter.js @@ -3,89 +3,85 @@ const util = require('util'); const jsdom = require('jsdom'); const fs = require('fs'); -describe('qg-filter', function () { +describe('qg-filter', function() { - require('../qg-filter.js'); - var widgetJS = widget; - const widgetHtml = 'widgets/qg-filter/qg-filter.html'; + require('../qg-filter.js'); + var widgetJS = widget; + const widgetHtml = 'widgets/qg-filter/qg-filter.html'; - // initialization - var prepareEnv = new Promise(function (resolve, reject) { - fs.readFile(widgetHtml, (err, dataBuffer) => { - if (err) { - reject(err) - } else { - resolve(dataBuffer.toString()); - } - }); - }).then(function (html) { - return new Promise(function (resolve, reject) { - jsdom.env( - { - html: html - , done: function (err, window) { - global.$ = require('jquery')(window); - if (err) { - reject(err) - } - else { - resolve(window); - } - } - }); - }); + // initialization + var prepareEnv = new Promise(function(resolve, reject) { + fs.readFile(widgetHtml, (err, dataBuffer) => { + if (err) { + reject(err) + } else { + resolve(dataBuffer.toString()); + } }); + }).then(function(html) { + const { window } = new jsdom.JSDOM(html); + global.$ = require('jquery')(window); + return Promise.resolve(window); + }); - it('should show 2 projects and 3 measures', function () { + it('should show 2 projects and 3 measures', function() { - return ( - prepareEnv.then(function (window) { - const root = window.document.documentElement; + return ( + prepareEnv.then(function(window) { + const root = window.document.documentElement; - widgetJS.onData(root, { - title: 'rainy day' - ,projects: [ - { - name: 'The Cursed Child' - ,metricsError : [ {count :1, metric:'enemies'}, {count :2, metric:'friends to liberate'} ] - }, - { - name: 'The Prisoner of Azkaban' - ,metricsError : [ {count :1, metric:'world to save'} ] - } - ] - }); + widgetJS.onData(root, { + title: 'rainy day', + projects: [{ + name: 'The Cursed Child', + metricsError: [{ + count: 1, + metric: 'enemies' + }, { + count: 2, + metric: 'friends to liberate' + }] + }, + { + name: 'The Prisoner of Azkaban', + metricsError: [{ + count: 1, + metric: 'world to save' + }] + } + ] + }); - assert.strictEqual( $($('.countRed', root).contents()[0]).text(), '2' ); - assert.strictEqual($('.countRed .title', root).html(), 'rainy day'); - assert.strictEqual($('.content .status', root).length, 2); - assert.strictEqual($('.content .status:eq(0) .statusName', root).html(), 'The Cursed Child' ); - assert.strictEqual($('.content .countRedSmall:eq(0) .metric', root).html(), 'enemies' ); - assert.strictEqual($($('.content .countRedSmall:eq(0)', root).contents()[0]).text(), '1' ); - assert.strictEqual($('.content .countRedSmall:eq(1) .metric', root).html(), 'friends to liberate' ); - assert.strictEqual($($('.content .countRedSmall:eq(1)', root).contents()[0]).text(), '2' ); - assert.strictEqual($('.content .status:eq(1) .statusName', root).html(), 'The Prisoner of Azkaban' ); - assert.strictEqual($('.content .countRedSmall:eq(2) .metric', root).html(), 'world to save' ); - assert.strictEqual($($('.content .countRedSmall:eq(2)', root).contents()[0]).text(), '1' ); - }) - ); + assert.strictEqual($($('.countRed', root).contents()[0]).text(), '2'); + assert.strictEqual($('.countRed .title', root).html(), 'rainy day'); + assert.strictEqual($('.content .status', root).length, 2); + assert.strictEqual($('.content .status:eq(0) .statusName', root).html(), 'The Cursed Child'); + assert.strictEqual($('.content .countRedSmall:eq(0) .metric', root).html(), 'enemies'); + assert.strictEqual($($('.content .countRedSmall:eq(0)', root).contents()[0]).text(), '1'); + assert.strictEqual($('.content .countRedSmall:eq(1) .metric', root).html(), 'friends to liberate'); + assert.strictEqual($($('.content .countRedSmall:eq(1)', root).contents()[0]).text(), '2'); + assert.strictEqual($('.content .status:eq(1) .statusName', root).html(), 'The Prisoner of Azkaban'); + assert.strictEqual($('.content .countRedSmall:eq(2) .metric', root).html(), 'world to save'); + assert.strictEqual($($('.content .countRedSmall:eq(2)', root).contents()[0]).text(), '1'); + }) + ); - }); + }); - it('should be green zero with the title', function () { - return ( - prepareEnv.then(function (window) { - const root = window.document.documentElement; + it('should be green zero with the title', function() { + return ( + prepareEnv.then(function(window) { + const root = window.document.documentElement; - widgetJS.onData(root, { - title: 'sunny day' - ,projects: [] - }); + widgetJS.onData(root, { + title: 'sunny day', + projects: [] + }); - assert.strictEqual( $($('.countGreen', root).contents()[0]).text(), '0' ); - assert.strictEqual($('.countGreen .title', root).html(), 'sunny day'); - assert.strictEqual($('.content .status', root).length, 0); - }) - ); - }); -}); \ No newline at end of file + assert.strictEqual($($('.countGreen', root).contents()[0]).text(), '0'); + assert.strictEqual($('.countGreen .title', root).html(), 'sunny day'); + assert.strictEqual($('.content .status', root).length, 0); + }) + ); + }); +});