React - Jenkins Wallboard (1/2)

Simple React Wallboard for Jenkins engined by a Node Server

A long time ago, for supervision purpose, I needed to display some real time KPIs retrieved from our Jenkins IC Platform on a large screen in our team’s open-space.
After several weeks of research, I didn’t find a suitable solution (wallboard or radiator plugins or apps). So I decided to code my own tool.
This project shows how I did it with React, Node and the Jenkins Json API.

Source code

The whole source code of this article is in my GitLab repository.

Architecture

We want to build an application which gets data from a Jenkins server.
This application will be built with React and will run in a browser displayed on a TV screen.
To avoid any XSS problem, we need a proxy (node server) between the webapp and the Jenkins server which is in another domain (IC Platform).

The proxy role is the following:

  • rendering the React application which will display our development KPIs
  • proxying the Jenkins server by offering a REST API which is redirected on the Jenkins REST API

General

graph LR; A(Client Browser) -.- B(Node Proxy) B -.- C(Jenkins Server)

Start-up

graph LR; A(Client Browser) --> |First request| B(Node Proxy) B --> |React App is sent|A

Running

graph LR; A(React App) --> |Rest request of Kpis| B(Node Proxy) B --> |Request forwarded to CI PF| C(Jenkins Server) C --> |Kpis are sent| B B --> |Kpis finally returned| A

Our project structure

Our project directory tree reflects exactly the previous architecture.

Indeed we create:

  • a client directory which will contain the React sources of our webapp
  • a server directory which will contain the Node sources of our webproxy

Prerequisites

To be able to use node and npm we must have installed

Modules

For server side:

For client side:

Our repository

First we create a git repository in a directory named node-react-jenkins-wallboard.
Then we create two directories, one for the server and one another for the client.

C:\>dir node-react-jenkins-wallboard
...
19/04/2018  08:37    <DIR>          client
19/04/2018  08:44    <DIR>          server
...

Node Proxy Web Server (1)

Create our proxy skeleton

Initialisation

In the server directory:

  1. we do a npm init to initialize our directory (we define our main file as server.js)
  2. we install express with npm install express --save
  3. we create a src directory in which we will put our sources

The package.json created by the init command must look like this:

{
  "name": "nrjw-server",
  "version": "1.0.0",
  "description": "Simple React Wallboard for Jenkins engined by a Node Server - Node Server Implementation",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Ent Wickler",
  "license": "ISC",
  "dependencies": {
    "express": "^4.16.3"
  }
}

Define a configuration file

We need to set parameters to run our server. So, in the src folder, we create now a config.js file with the very short following content:

'use strict';

/* */
module.exports = {
	'proxyPort': '3001'
}

The directory tree at this point
└── _node-react-jenkins-wallboard
    ├── _client
    └── _server
        ├── _node_modules
        ├── _src
        |    └── config.js
        └── package.json
Define a main file

Now let’s create our server.js file in the server folder.
For that moment we don’t use the config file yet. We hard code the proxy port to 3000.

'use strict';

/* globals */
global.__BASE = __dirname ;

/* */
var express = require('express') ;
var app = express() ;

/* */
app.listen(3000, function () {
  console.log('Proxy server started on port [%d]', 3000);
});

Define a usage file

Let’s create the Usage.js file in the src folder like the following:

'use strict';

/* we will use path.join for our requires to be posix and windows compatible */
var path = require('path');

/* get config settings */
var myConfig = require(path.join((__BASE + '/src/config')));

/* */
class Usage {

  /* */
  constructor() {
    this._argv;
    this._proxyPort;
    this._initProps();
  }

  /* */
  _initProps() {
    this._argv = process.argv;

    for (let i = 2; i < this._argv.length; i++) {
      if (this._argv[i] === '--p' || this._argv[i] === '--port') {
        this._proxyPort = this._argv[i + 1];
      }
    }

  }

  /* */
  check() {
    for (let i = 2; i < this._argv.length; i++) {
      if (this._argv[i] === '--h' || this._argv[i] === "--help") {
        console.log('');        
        console.log('Usage :');
        console.log('-------');
        console.log('');
        console.log('--h (--help)                :  displays this help');
        console.log('--p (--port) <port number>  :  sets proxy port number (if not : config file value or 3000 by default)');
        console.log('');
        console.log('Usage examples : start server --help');
        console.log('               : start server --p 3000');
        process.exit(0);
      }
    }

    console.log('Get more info with "node server --h"');
  }

  /* */
  get proxyPort() {
    if (this._proxyPort) { return this._proxyPort; }
    if (myConfig.proxyPort) { return myConfig.proxyPort; }
    return 3000;
  }

}

/* */
exports.Usage = Usage;
This new Usage class uses the config.js file to retrieve the setting port and to give help to the user if he asks for it.

Let’s assemble the code

We modify the server.js file to check the use of the start command and to retrieve the port value of the proxy server.

Now we have with the following content:

'use strict';

/* globals */
global.__BASE = __dirname ;

/* we will use path.join for our requires to be posix and windows compatible */
var path = require('path');

/* usage verification */
var { Usage } = require(path.join((__BASE + '/src/Usage')));
var myUsage = new Usage();
myUsage.check();

/* */
var express = require('express') ;
var app = express() ;

/* */
app.listen(myUsage.proxyPort, function () {
  console.log('Proxy server started on port [%d]', myUsage.proxyPort);
});

Again modify the package.json file

Because I develop/build under WINDOWS and I run my tools under a LINUX platform, my code must be compatible with both POSIX and WINDOWS platforms.

For that reason I had to modify the content of the package.json file like this:

{
  "name": "nrjw-server",
  "version": "1.0.0",
  "description": "Simple React Wallboard for Jenkins engined by a Node Server - Node Server Implementation",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "NODE_ENV=production node server.js",
    "start_windows": "set NODE_ENV=production&&node server.js",
    "start_development": "NODE_ENV=development node server.js",
    "start_windows_development": "set NODE_ENV=development&&node server.js"
  },
  "scriptsComment": {
    "start": "npm run start: start the server for PROD environment on LINUX platform",
    "start_windows": "npm run start_windows: start the server for PROD environment on WINDOWS platform",
    "start_development": "npm run start_development: start the server for DEVT environment on LINUX platform",
    "start_windows_development": "npm run start_windows_development: start the server for DEVT environment on WINDOWS platform"
  },
  "author": "Ent Wickler",
  "license": "ISC",
  "dependencies": {
    "express": "^4.16.3"
  }
}

Start the server

From now on Windows, we can run the server like this:

c:\node-react-jenkins-wallboard\server>node server --port 3006

or like this (the double -- -- is not a syntax error):

c:\node-react-jenkins-wallboard\server>npm run start_windows -- --port 3006
The directory tree at this point
└── node-react-jenkins-wallboard
    ├── client
    └── server
        ├── node_modules
        ├── src
        |    ├── config.js
        |    ├── Toolbox.js
        |    └── Usage.js
        ├── package.json
        └── server.js

Add content to the proxy

Now we have our skeleton which runs well, we have to implement the Proxy REST Api which

  1. will be requested by our future REACT client
  2. will call the Jenkins REST Api to retrieve KPIs
  3. will forward KPIs datas to the REACT client
Add a cache

Since

  • we use pull REST Api methods
  • we don’t control the number of client calls
  • we want to protect the Jenkins server from heavy load

we add a very simple cache feature in our proxy server.
This cache function will request the Jenkins server only one time by x seconds even if there are many client requests to the proxy.

First we add a new file named Kpi.js int the src folder which contains the class definition of the object which will request the REST Json Api of Jenkins.
For that moment it contains only one countdown property and a function which prints the countdown value in the console.

Here is this file:

'use strict';

/* */
class Kpi {

    /* */
    constructor() {
        this._countdown;
        this._initProps();
    }

    /* */
    _initProps() {
        this._countdown = 0;
    }

    /* */
    refresh() {
        this._countdown++;
        console.log('Kpi refresh n°%i', this._countdown);
    }
}

/* */
exports.Kpi = Kpi;

We also modify the server.js file to add the call to the refresh function of the Kpi object:

'use strict';

/* globals */
global.__BASE = __dirname;

/* we will use path.join for our requires to be posix and windows compatible */
var path = require('path');

/* usage verification */
var { Usage } = require(path.join((__BASE + '/src/Usage')));
var myUsage = new Usage();
myUsage.check();

/* object which retrieves jenkins kpis */
var { Kpi } = require(path.join((__BASE + '/src/Kpi')));
var myKpi = new Kpi();

/* run the cache update every 15 seconds */
function refreshCache() {
  myKpi.refresh();
}
setInterval(refreshCache, 15000);

/* */
var express = require('express');
var app = express();

/* */
app.listen(myUsage.proxyPort, function () {
  console.log('Proxy server started on port [%d]', myUsage.proxyPort);
});

Test

In the command prompt, run node server.
The result must look like this (one refresh printed every 15 seconds):

Get more info with "node server --h"
Proxy server started on port [3001]
Kpi refresh n°1
Kpi refresh n°2
Kpi refresh n°3
Kpi refresh n°4
Kpi refresh n°5
The directory tree at this point
└── node-react-jenkins-wallboard
    ├── client
    └── server
        ├── node_modules
        ├── src
        |    ├── config.js
        |    ├── Kpi.js
        |    ├── Toolbox.js
        |    └── Usage.js
        ├── package.json
        └── server.js

Mocking the Jenkins server

When I was developing this tool I had several Jenkins test instances.
But for this proof of concept the easiest way to do is to implement a very trivial fake server which replaces a Jenkins instance.
This mock is simply a node server which listens on a port and sends json responses in real Jenkins format.

Create the fake server

In the jenkins directory:

  1. we do a npm init to initialize our directory (we define our main file as jenkins.js)
  2. we install express with npm install express --save

So we create now the jenkins.js file with the following content (very ugly hard code):

'use strict';

/* */
var express = require('express');
var app = express();

var buildsForBigProject =
    {
        "_class": "com.Instancebees.hudson.plugins.folder.Folder",
        "views": [
            {
                "_class": "hudson.model.AllView",
                "jobs": [
                    {
                        "_class": "com.github.mjdetullio.jenkins.plugins.multibranch.FreeStyleMultiBranchProject",
                        "url": "http://my.jenkins.domain.url/job/BigProject/job/BUILD/job/InstanceDeploy/"
                    },
                    {
                        "_class": "com.github.mjdetullio.jenkins.plugins.multibranch.FreeStyleMultiBranchProject",
                        "url": "http://my.jenkins.domain.url/job/BigProject/job/BUILD/job/InstanceDispatch/"
                    },
                    {
                        "_class": "com.github.mjdetullio.jenkins.plugins.multibranch.FreeStyleMultiBranchProject",
                        "url": "http://my.jenkins.domain.url/job/BigProject/job/BUILD/job/InstanceMemory/"
                    }
                ],
                "name": "All"
            },
            {
                "_class": "hudson.model.ListView",
                "jobs": [
                    {
                        "_class": "hudson.model.FreeStyleProject",
                        "url": "http://my.jenkins.domain.url/job/BigProject/job/BUILD/job/InstanceDeploy/job/version2_release/",
                        "color": "blue"
                    },
                    {
                        "_class": "hudson.model.FreeStyleProject",
                        "url": "http://my.jenkins.domain.url/job/BigProject/job/BUILD/job/InstanceDispatch/job/version2_release/",
                        "color": "blue"
                    },
                    {
                        "_class": "hudson.model.FreeStyleProject",
                        "url": "http://my.jenkins.domain.url/job/BigProject/job/BUILD/job/InstanceMemory/job/version2_release/",
                        "color": "blue"
                    }
                ],
                "name": "version1"
            },
            {
                "_class": "hudson.model.ListView",
                "jobs": [
                    {
                        "_class": "hudson.model.FreeStyleProject",
                        "url": "http://my.jenkins.domain.url/job/BigProject/job/BUILD/job/InstanceDeploy/job/version2_release/",
                        "color": "blue"
                    },
                    {
                        "_class": "hudson.model.FreeStyleProject",
                        "url": "http://my.jenkins.domain.url/job/BigProject/job/BUILD/job/InstanceDispatch/job/version2_release/",
                        "color": "blue"
                    },
                    {
                        "_class": "hudson.model.FreeStyleProject",
                        "url": "http://my.jenkins.domain.url/job/BigProject/job/BUILD/job/InstanceMemory/job/version2_release/",
                        "color": "red"
                    }
                ],
                "name": "version2"
            },
            {
                "_class": "hudson.model.ListView",
                "jobs": [
                    {
                        "_class": "hudson.model.FreeStyleProject",
                        "url": "http://my.jenkins.domain.url/job/BigProject/job/BUILD/job/InstanceDeploy/job/version3_release/",
                        "color": "blue_anime"
                    },
                    {
                        "_class": "hudson.model.FreeStyleProject",
                        "url": "http://my.jenkins.domain.url/job/BigProject/job/BUILD/job/InstanceDispatch/job/version3_release/",
                        "color": "blue"
                    },
                    {
                        "_class": "hudson.model.FreeStyleProject",
                        "url": "http://my.jenkins.domain.url/job/BigProject/job/BUILD/job/InstanceMemory/job/version3_release/",
                        "color": "blue"
                    }
                ],
                "name": "version3"
            }
        ]
    }

var buildsForSmallProject =
    {
        "_class": "com.cloudbees.hudson.plugins.folder.Folder",
        "views": [
            {
                "_class": "hudson.model.AllView",
                "jobs": [
                    {
                        "_class": "hudson.maven.MavenModuleSet",
                        "url": "http:/my.jenkins.domain.url/job/SmallProject/job/SmallProjectWS/",
                        "color": "blue"
                    },
                    {
                        "_class": "hudson.maven.MavenModuleSet",
                        "url": "http:/my.jenkins.domain.url/job/SmallProject/job/SmallProjectEngine/",
                        "color": "blue"
                    },
                    {
                        "_class": "hudson.maven.MavenModuleSet",
                        "url": "http:/my.jenkins.domain.url/job/SmallProject/job/SmallProjectData/",
                        "color": "blue"
                    },
                    {
                        "_class": "hudson.maven.MavenModuleSet",
                        "url": "http:/my.jenkins.domain.url/job/SmallProject/job/SmallProjectDispatcher/",
                        "color": "blue"
                    }
                ],
                "name": "All"
            }
        ]
    }

var metricsForBigProject =
    {
        "_class": "hudson.model.FreeStyleProject",
        "builds": [
            {
                "_class": "hudson.model.FreeStyleBuild",
                "actions": [
                    {
                        "_class": "hudson.model.CauseAction"
                    },
                    {
                        "_class": "hudson.tasks.junit.TestResultAction",
                        "failCount": 0,
                        "skipCount": 8,
                        "totalCount": 2590
                    }
                ],
                "number": 14356
            },
            {
                "_class": "hudson.model.FreeStyleBuild",
                "actions": [
                    {
                        "_class": "hudson.model.CauseAction"
                    },
                    {
                        "_class": "hudson.tasks.junit.TestResultAction",
                        "failCount": 0,
                        "skipCount": 8,
                        "totalCount": 2590
                    }
                ],
                "number": 14355
            },
            {
                "_class": "hudson.model.FreeStyleBuild",
                "actions": [
                    {
                        "_class": "hudson.model.CauseAction"
                    },
                    {
                        "_class": "hudson.tasks.junit.TestResultAction",
                        "failCount": 0,
                        "skipCount": 8,
                        "totalCount": 2590
                    }
                ],
                "number": 14354
            }
        ]
    }

var metricsForSmallProject =
    {
        "_class": "hudson.model.FreeStyleProject",
        "builds": [
            {
                "_class": "hudson.model.FreeStyleBuild",
                "actions": [
                    {
                        "_class": "hudson.model.CauseAction"
                    },
                    {
                        "_class": "hudson.tasks.junit.TestResultAction",
                        "failCount": 4,
                        "skipCount": 1,
                        "totalCount": 5545
                    }
                ],
                "number": 2222
            },
            {
                "_class": "hudson.model.FreeStyleBuild",
                "actions": [
                    {
                        "_class": "hudson.model.CauseAction"
                    },
                    {
                        "_class": "hudson.tasks.junit.TestResultAction",
                        "failCount": 0,
                        "skipCount": 1,
                        "totalCount": 5545
                    }
                ],
                "number": 2221
            }
        ]
    }



app.get('/jenkins/builds/:projectname', function (req, res) {
    console.log("the jenkins url to get the list of builds should look like => ");
    console.log("http://my.jenkins.domain.url/job/my_project_name/api/json?pretty=true&tree=views[name,jobs[url,color]]")

    if (req.params.projectname === 'BigProject') {
        res.status(200).json(buildsForBigProject);
        return;
    }

    res.status(200).json(buildsForSmallProject);
    return;
});

app.get('/jenkins/metrics/:projectname', function (req, res) {
    console.log("the jenkins url to get the list of metrics (test results for instance) should look like => ");
    console.log("http://my.jenkins.domain.url/job/my_project_name/job/my_job_name/api/json?&pretty=true&tree=builds[number,actions[failCount,skipCount,totalCount]]")
    if (req.params.projectname === 'BigProject') {
        res.status(200).json(metricsForBigProject);
        return;
    }

    res.status(200).json(metricsForSmallProject);
    return;
});

/* */
app.listen(3999, function () {
    console.log('Fake Jenkins server started on port [%d]', 3999);
});

Now, we have 2 Rest Jenkins methods, one for listing builds and on another to list metrics.

The directory tree at this point

└── node-react-jenkins-wallboard
    ├── client
    └── server
        ├── node_modules
        ├── src
        |    ├── config.js
        |    ├── Kpi.js
        |    ├── Toolbox.js
        |    └── Usage.js
        ├── package.json
        └── server.js
    └── jenkins
        ├── node_modules
        ├── package.json
        └── jenkins.js

Node Proxy Web Server (2)

We are going to modify our node proxy to get KPIs from the mocked Jenkins server.

Promises

Because promises are cool, in the server directory, we install request-promise with npm install request request-promise --save.
The package.json file should look like this:

{
  "name": "nrjw-server",
  "version": "1.0.0",
  "description": "Simple React Wallboard for Jenkins engined by a Node Server - Node Server Implementation",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "NODE_ENV=production node server.js",
    "start_windows": "set NODE_ENV=production&&node server.js",
    "start_development": "NODE_ENV=development node server.js",
    "start_windows_development": "set NODE_ENV=development&&node server.js"
  },
  "scriptsComment": {
    "start": "npm run start: start the server for PROD environment on LINUX platform",
    "start_windows": "npm run start_windows: start the server for PROD environment on WINDOWS platform",
    "start_development": "npm run start_development: start the server for DEVT environment on LINUX platform",
    "start_windows_development": "npm run start_windows_development: start the server for DEVT environment on WINDOWS platform"
  },
  "author": "Ent Wickler",
  "license": "ISC",
  "dependencies": {
    "express": "^4.16.3",
    "request": "^2.85.0",
    "request-promise": "^4.2.2"
  }
}

Setting jenkins url

We modify config.js file to add all our Jenkins URL. So it looks like the following content:

'use strict';

/* */
module.exports = {

	'proxyPort': '3001',

	'urlJenkinsMetricsList': [
		{ 'project': 'BigProject', 'job': 'BigProject-Metrics', 'url': 'http://localhost:3999/jenkins/metrics/bigproject' },
		{ 'project': 'SmallProject', 'job': 'SmallProject-Metrics', 'url': 'http://localhost:3999/jenkins/metrics/smallproject' }
	],

	'urlJenkinsBuildsList': [
		{ 'project': 'BigProject', 'type': 'BUILD', 'url': 'http://localhost:3999/jenkins/builds/bigproject' },
		{ 'project': 'SmallProject', 'type': 'BUILD', 'url': 'http://localhost:3999/jenkins/builds/smallproject' }
	]

}

Server modification

We add the REST routing like this:

'use strict';

/* globals */
global.__BASE = __dirname;

/* we will use path.join for our requires to be posix and windows compatible */
var path = require('path');

/* usage verification */
var { Usage } = require(path.join((__BASE + '/src/Usage')));
var myUsage = new Usage();
myUsage.check();

/* object which retrieves jenkins kpis */
var { Kpi } = require(path.join((__BASE + '/src/Kpi')));
var myKpi = new Kpi();

/* run the cache update every 15 seconds */
function refreshCache() {
  myKpi.refresh();
}
setInterval(refreshCache, 15000);

/* */
var express = require('express');
var app = express();

/* get jenkins kpis */
app.get('/kpis', function (req, res) {
	res.status(200).json(myKpi);
	return;
});

/* */
app.listen(myUsage.proxyPort, function () {
  console.log('Proxy server started on port [%d]', myUsage.proxyPort);
});

Kpi class content

'use strict';

/* */
var request = require('request-promise');

/* we will use path.join for our requires to be posix and windows compatible */
var path = require('path');

/* get config settings */
var myConfig = require(path.join((__BASE + '/src/config')));

/* */
class Kpi {

  /* */
  constructor() {
    this.metrics; // tests kpis
    this.builds; // builds kpis

    this._initProps();
  }

  /* */
  _initProps(pMoment) {
    this.metrics = [];
    this.builds = [];
  }

  /* the jenkins anonymous account must have the read role */
  async refresh() {
    let metricsByProject = [];
    let buildsByProject = [];

    /* metrics */
    let metricsRequests = myConfig.urlJenkinsMetricsList.map(async function (mapRow) {
      let options = { url: mapRow.url };
      let body;

      try {
        body = await request.get(options);
      } catch (error) {
        console.error(error);
        return;
      }

      let json = JSON.parse(body);

      // The last build is builds[0]
      let actions = json.builds[0].actions;
      let metrics = {};
      for (let i = 0; i < actions.length; i++) {
        if (actions[i].totalCount) {
          metrics.failCount = actions[i].failCount;
          metrics.skipCount = actions[i].skipCount;
          metrics.totalCount = actions[i].totalCount;
        }
      }

      metricsByProject.push({ 'project': mapRow.project, 'job': mapRow.job, 'metrics': metrics });
    });

    try {
      await Promise.all(metricsRequests);
    } catch (error) {
      console.error(error);
      return;
    }

    this.metrics = metricsByProject.sort(function (metricA, metricB) {
      let projectA = metricA.project.toLowerCase();
      let projectB = metricB.project.toLowerCase();
      if (projectA < projectB) { return -1; }
      if (projectA > projectB) { return 1; }
      return 0;
    });

    /* builds */
    let buildsRequests = myConfig.urlJenkinsBuildsList.map(async function (mapRow) {
      let options = { url: mapRow.url };
      let body;

      try {
        body = await request.get(options);
      } catch (error) {
        console.error(error);
        return;
      }

      let json = JSON.parse(body);

      let views = json.views;
      let builds = [];
      let status;

      // we analyse all the views
      for (let i = 0; i < views.length; i++) {
        let error = false;
        let running = false;
        let buildsSuccess = [];
        let buildsError = [];
        let buildsRunning = [];
        let buildsList = [];

        // Builds in error ?
        for (let j = 0; j < views[i].jobs.length; j++) {

          if (views[i].jobs[j].color) {

            if (views[i].jobs[j].color === 'blue') {
              buildsSuccess.push(views[i].jobs[j].url);
            }

            if (views[i].jobs[j].color === 'red') {
              error = true;
              buildsError.push(views[i].jobs[j].url);
            }

            if (views[i].jobs[j].color.indexOf("anime") != -1) {
              running = true;
              buildsRunning.push(views[i].jobs[j].url);
            }
          }
        }

        // by priority order
        // 1 - running
        // 2 - error
        // 3 - success
        if (!error && !running) {
          status = 'success';
          buildsList = buildsSuccess;
        }
        if (error) {
          status = 'error';
          buildsList = buildsError;
        }
        if (running) {
          status = 'running';
          buildsList = buildsRunning;
        }

        builds.push({ 'view': views[i].name, 'status': status, 'builds': buildsList });
      }

      buildsByProject.push({ 'project': mapRow.project, 'type': mapRow.type, 'builds': builds });

    });

    try {
      await Promise.all(buildsRequests);
    } catch (error) {
      console.error(error);
      return;
    }

    this.builds = buildsByProject.sort(function (buildA, buildB) {
      let projectA = buildA.project.toLowerCase() + buildA.type.toLowerCase();
      let projectB = buildB.project.toLowerCase() + buildB.type.toLowerCase();;
      if (projectA < projectB) { return -1; }
      if (projectA > projectB) { return 1; }
      return 0;
    });
  }

}

exports.Kpi = Kpi;

Testing

We start

  1. in the jenkins directory, run the jenkins server node jenkins (listening on port 3999)
  2. in the server directory, run the jenkins server node server (listening on port 3001)

In your brower, type http://localhost:3001/kpis then you get:

{
    "metrics": [
        {
            "project": "BigProject",
            "job": "BigProject-Metrics",
            "metrics": {
                "failCount": 0,
                "skipCount": 8,
                "totalCount": 2590
            }
        },
        {
            "project": "SmallProject",
            "job": "SmallProject-Metrics",
            "metrics": {
                "failCount": 4,
                "skipCount": 1,
                "totalCount": 5545
            }
        }
    ],
    "builds": [
        {
            "project": "BigProject",
            "type": "BUILD",
            "builds": [
                {
                    "view": "All",
                    "status": "success",
                    "builds": []
                },
                {
                    "view": "version1",
                    "status": "success",
                    "builds": [
                        "http://my.jenkins.domain.url/job/BigProject/job/BUILD/job/InstanceDeploy/job/version2_release/",
                        "http://my.jenkins.domain.url/job/BigProject/job/BUILD/job/InstanceDispatch/job/version2_release/",
                        "http://my.jenkins.domain.url/job/BigProject/job/BUILD/job/InstanceMemory/job/version2_release/"
                    ]
                },
                {
                    "view": "version2",
                    "status": "error",
                    "builds": [
                        "http://my.jenkins.domain.url/job/BigProject/job/BUILD/job/InstanceMemory/job/version2_release/"
                    ]
                },
                {
                    "view": "version3",
                    "status": "running",
                    "builds": [
                        "http://my.jenkins.domain.url/job/BigProject/job/BUILD/job/InstanceDeploy/job/version3_release/"
                    ]
                }
            ]
        },
        {
            "project": "SmallProject",
            "type": "BUILD",
            "builds": [
                {
                    "view": "All",
                    "status": "success",
                    "builds": [
                        "http:/my.jenkins.domain.url/job/SmallProject/job/SmallProjectWS/",
                        "http:/my.jenkins.domain.url/job/SmallProject/job/SmallProjectEngine/",
                        "http:/my.jenkins.domain.url/job/SmallProject/job/SmallProjectData/",
                        "http:/my.jenkins.domain.url/job/SmallProject/job/SmallProjectDispatcher/"
                    ]
                }
            ]
        }
    ]
}

For the REACT coding you have to read the second part of this tutorial.

comments powered by Disqus