Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Jenkins - Build status history for multiple jobs
class Dashing.Buildhistory extends Dashing.Widget
ready: ->
# This is fired when the widget is done being rendered
onData: (data) ->
# This is fired when the widget receives data
#extract widget paramaters
@max_displayed_samples = @get('max_samples')
@display_job_titles = @get('show_job_titles')
@display_sample_spacers = @get('sample_spacers')
container = $(@node).parent()
# code from graph.coffee
widget_width = (Dashing.widget_base_dimensions[0] * container.data("sizex")) + Dashing.widget_margins[0] * 2 * (container.data("sizex") - 1)
widget_height = (Dashing.widget_base_dimensions[1] * container.data("sizey"))
chart_inner_margin = 40
#calculate canvas size
canvas_width = widget_width - chart_inner_margin
canvas_height = widget_height - chart_inner_margin
#calculate sample bar width
sample_bar_width = 10
unless @max_displayed_samples is 0
#adjust if spacers between samples
if @display_sample_spacers
sample_bar_width = (canvas_width - @max_displayed_samples) / @max_displayed_samples
else
sample_bar_width = canvas_width / @max_displayed_samples
#calculate sample bar height
row_height = 30
@number_jenkins_jobs = data.jenkins_jobs.length
unless @number_jenkins_jobs is 0
#adjust if spacers between samples
if @display_sample_spacers
row_height = (canvas_height - @number_jenkins_jobs) / @number_jenkins_jobs
else
row_height = canvas_height / @number_jenkins_jobs
#hash Jenkins build status values ('ball color' => HTML colour values)
#reference: https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/hudson/model/BallColor.java
#use a green sample colour for success as opposed to native Jenkins 'blue'
#NOTE: disabled, aborted and not built states map to the same colour for simplicity
#all 'xxx_anime' colours represent currently building jobs.
#here, they all map to the same sample colour for simplicity
@jenkins_job_status_colour_map =
red: "#FE2E2E"
yellow: "#FACC2E"
blue: "#64FE2E"
grey: "#A4A4A4"
disabled: "#6E6E6E"
aborted: "#6E6E6E"
nobuilt: "#6E6E6E"
red_anime: "#D8D8D8"
blue_anime: "#D8D8D8"
grey_anime: "#D8D8D8"
disabled_anime: "#D8D8D8"
aborted_anime: "#D8D8D8"
nobuilt_anime: "#D8D8D8"
#remove child elements of previously rendered canvas object
if $("#buildhistorychart")
$("#buildhistorychart").empty()
#create new canvas object
@raphael_canvas = Raphael("buildhistorychart", canvas_width, canvas_height )
y_index = 0
#loop through job status data and render to widget
for job_entry of data.jenkins_jobs
extracted_job_entry = data.jenkins_jobs[job_entry]
#render status history for an entire job
@_render_job_status_row @raphael_canvas, 0, y_index, sample_bar_width, row_height, extracted_job_entry
#increment row
y_index = y_index + row_height
#add spacer if required
if @display_sample_spacers
y_index = y_index + 1
# This function renders the build status history for a single job
_render_job_status_row: (@raphael_canvas, row_x, row_y, sample_bar_width, height, job_object) ->
#draw status sample bars
num_samples = job_object.build_status.length
x_index = row_x
#only draw samples if there is data to be drawn...
unless num_samples is 0
for job_status_entry of job_object.build_status
job_status = job_object.build_status[job_status_entry]
#render rectangle for single sample
rect_attributes =
stroke: "none"
fill: @jenkins_job_status_colour_map[job_status.status]
@raphael_canvas.rect(x_index, row_y, sample_bar_width, height).attr rect_attributes
#draw job title if required
if @display_job_titles
@_render_job_title(@raphael_canvas, row_x, row_y, height, job_object.job_name)
#increment X index
x_index = x_index + sample_bar_width
#add spacer if required
if @display_sample_spacers
x_index = x_index + 1
#this function draws a job title
_render_job_title: (@raphael_canvas, row_x, row_y, height, title) ->
#job title text font
txt_attributes =
font: "8px Fontin-Sans, Arial"
stroke: "none"
fill: "#000"
"text-anchor": "start"
opacity: 0.03
#render job title text
@raphael_canvas.text(row_x, row_y + (height / 2), title).attr txt_attributes
<div id="buildhistorychart"></div>
require 'csv'
require 'net/http'
require 'json'
#constants
JOB_STATUS_HISTORY_FILENAME = 'job_status_history.csv'
NUMBER_SAMPLES_IN_HISTORY = 100
JENKINS_BUILD_STATUS_HISTORY_URI = URI.parse("http://<YOUR-JENKINS-URL>:8080")
#Trim thresholds (widget display)
MAX_FILENAME_LENGTH = 30
FILENAME_TAIL_LENGTH = 20
#array of all unit test jobs to be processed
$jenkins_jobs_to_be_tracked = [
'My main build',
'My auxiliary build',
'Sanity tests',
'Unit tests'
'Code coverage'
]
###################################################################################
# Sub routines
###################################################################################
#helper function that trims file names
#for long filenames, this function keeps all chars up to the
#trim length, inserts an ellipsis and then keeps the "tail" of the file name
def trim_filename(filename)
filename_length = filename.length
#trim 'n' splice if necessary
if filename_length > MAX_FILENAME_LENGTH
filename = filename.to_s[0..MAX_FILENAME_LENGTH] + '...' + filename.to_s[(filename_length - FILENAME_TAIL_LENGTH)..filename_length]
end
return filename
end
#this helper function loads the CSV file and creates a JSON message containing build status history info
def get_build_status_json_from_csv_file
build_status_history = Hash.new
jenkins_job_entries = Array.new
job_index = 0
CSV.foreach(JOB_STATUS_HISTORY_FILENAME) do |job_status_history_row|
#extract job name
job_name = job_status_history_row[0]
#create object for Jenkins job
job_history_entry = Hash.new
job_history_entry["job_name"] = trim_filename(job_name)
job_status_entries = Array.new
#iterate across status values for the job_status_history_row
for status_index in 1 ... job_status_history_row.size
job_status_item = job_status_history_row[status_index]
job_status_entries << {"status" => job_status_item}
end
job_history_entry.merge!("build_status" => job_status_entries)
jenkins_job_entries << job_history_entry
#increment job index
job_index = job_index + 1
end
build_status_history.merge!("jenkins_jobs" => jenkins_job_entries)
end
# This function appends a new set of job status into to the working file
# and trims any old data.
# latest_jenkins_job_status_map is a hash containing Jenkins job name & most recent Jenkins status value )
def update_job_status_history_csv_file(latest_jenkins_job_status_map)
job_status_history = Array.new
#check if file exists - if not, create a RAM structure
if (File.file?(JOB_STATUS_HISTORY_FILENAME) == false)
#build array
$jenkins_jobs_to_be_tracked.each do |job_name|
job_entry = Array.new
job_entry.push job_name
job_status_history.push job_entry
end
else
job_status_history = CSV.read(JOB_STATUS_HISTORY_FILENAME)
#clean out source file
File.delete(JOB_STATUS_HISTORY_FILENAME)
end
#loop through rows of job status info and process
job_index = 0
job_status_history.each do |job_status_history_row|
#extract job name
job_name = job_status_history_row[0]
#check if we need to trim the job status history
if (job_status_history_row.size > NUMBER_SAMPLES_IN_HISTORY)
#delete old history item
job_status_history_row.delete_at(1)
#append new status value to end of row
job_status_history_row << latest_jenkins_job_status_map[job_name]
else
#less than a full set of status history - simply append the new status info
job_status_history_row << latest_jenkins_job_status_map[job_name]
job_index = job_index + 1
end
#write out data to file
out_file = File.new(JOB_STATUS_HISTORY_FILENAME, "a")
element_index = 1
job_status_history_row.each do |element|
out_file << element
if (element_index == job_status_history_row.length)
out_file << "\n"
else
out_file << ","
end
element_index = element_index + 1
end
out_file.close
end
end
#this helper function searches through the jobs entries looking for a specific name
# extracting the build status if found
def search_for_job_in_full_job_list_json(json_summary, job_name, job_status_hash_table)
json_summary["jobs"].each do |job_entry|
if (job_entry["name"] == job_name)
#push new entry into hash table
job_status_entry = {job_entry["name"] => job_entry["color"]}
job_status_hash_table.merge!(job_status_entry)
else
#print "No match [#{job_entry}]\n"
end
end
end
SCHEDULER.every '60s', :first_in => 0 do
num_jobs = $jenkins_jobs_to_be_tracked.count()
if num_jobs == 0
raise 'Number of jobs is zero - you must monitor at least one job!'
end
if NUMBER_SAMPLES_IN_HISTORY == 0
raise 'Number of samples in history is zero - you must have at least one sample!'
end
#Fetch job status info from Jenkins (full job list)
http = Net::HTTP.new(JENKINS_BUILD_STATUS_HISTORY_URI.host, JENKINS_BUILD_STATUS_HISTORY_URI.port)
request = Net::HTTP::Get.new("/api/json?tree=jobs[name,color]")
response = http.request(request)
build_info = JSON.parse(response.body)
$most_recent_jenkins_status_map = Hash.new
#Search for specific jobs within overall job list
$jenkins_jobs_to_be_tracked.each do |jenkins_job_name|
search_for_job_in_full_job_list_json(build_info, jenkins_job_name, $most_recent_jenkins_status_map)
end
print "Updating job status history (file) ...\n"
update_job_status_history_csv_file($most_recent_jenkins_status_map)
print "loading job status history ...\n"
$build_status_history_json = get_build_status_json_from_csv_file
print "Sending event to build history widget ...\n"
send_event('buildhistory', $build_status_history_json)
end
// ----------------------------------------------------------------------------
// Sass declarations
// ----------------------------------------------------------------------------
$background-color: #454746;
// ----------------------------------------------------------------------------
// Widget-image styles
// ----------------------------------------------------------------------------
.widget-buildhistory {
background-color: $background-color;
}

Jenkins Build Status History - screenshot

Description

The Jenkins Build Status History widget periodically fetches a snapshot of build status information for a specified list of jobs on a Jenkins CI server.

As time progresses, new build status samples are added to the right, while older samples are removed from the left. This view allows you to quickly see the health of your jobs as well as any time trends.

Calls are made to the Jenkins API to retrieve the name and color properties of listed jobs in a JSON form. The color of a given job represents its current build status (i.e. "blue" => successful build, "red" => failing build and so on).

The build colours are rendered within the widget using the Raphaël JavaScript library for vector graphics.

This widget borrows some code from the Rickshaw widget.

Installing the widget

Raphaël

You need the Raphaël library to use the widget. Get the latest version here.

Place raphael.min.js in your assets/javascripts folder

Widget files

Place the following files in a folder called widgets/buildhistory:

  • buildhistory.coffee
  • buildhistory.html
  • buildhistory.scss

Build history retrieval job

Place the following file in your jobs/ folder:

  • buildhistory.rb

Adding the Jenkins Last Commit widget to your dashboard

To add this widget to your dashboard, add the following to your <Dashboard-filename>.erb file:

    <li data-row="1" data-col="1" data-sizex="1" data-sizey="1">
        <div data-id="buildhistory" data-view="Buildhistory" data-max_samples="100" data-show_job_titles="true" data-sample_spacers="true"></div>
    </li>

Notice that the widget has a few parameters:

Parameter Meaning
data-max_samples Number of build status samples to store before discarding old values
data-show_job_titles true if job titles must be rendered on widget, false if no titles to be rendered
data-sample_spacers If true then a single space is inserted between samples & rows.

Configuring the widget for use with your Jenkins instance

There are a few parameters that must be set up before using this widget with your Jenkins instance.

In the buildhistory.rb file, modify the following parameters according to your needs:

Parameter Meaning
JENKINS_BUILD_STATUS_HISTORY_URI Jenkins base URL
$jenkins_jobs_to_be_tracked This is an array containing job names to be tracked on Jenkins
@mg786

This comment has been minimized.

Copy link

mg786 commented Sep 23, 2014

I keep getting scheduler caught exception:
746: unexpected token at '<title>Apache Tomcat/7.0.54 - The requested resouce is not availiable

It is caused by a `block in<top (required)>' in line 172 of the buildhistory.rb

Do yo know how to get round this?

@kalenwatermeyer

This comment has been minimized.

Copy link
Owner Author

kalenwatermeyer commented Oct 8, 2014

Hi @mg786 - just a quick question:

Did you change the JENKINS_BUILD_STATUS_HISTORY_URI constant on line 8 to match your Jenkins server's URL?

(I ask because the above code contains a dummy URL)

@TinajaLabs

This comment has been minimized.

Copy link

TinajaLabs commented Jan 22, 2015

This widget seems to depend on a list of specific job names. Is there any way to point to all jobs and return red if ANY builds are failing?

Thanks for any tips, Chris.

@kalenwatermeyer

This comment has been minimized.

Copy link
Owner Author

kalenwatermeyer commented Feb 2, 2015

Hi @TinajaLabs,

You're right, this widget is focused on retrieving status information for specific jobs. The reason for this is so that you can compare the relative health of a set of jobs along with timing information.

To do an 'overview' health check for all jobs, I would recommend fetching the dashboard status from your Jenkins root URL (e.g http://your-jenkins-instance:8080/api/json).

This will return a list of your jobs, along with their build status info (color = red, color = blue etc).

You could then add some logic to indicate to your dashboard users that one or more builds are currently failing.

Hope this makes sense!

@MrPeppa1

This comment has been minimized.

Copy link

MrPeppa1 commented Feb 18, 2015

hi, is there a way to split them in two rows ? because i have so many jobs, that the names are not readable...

@javedahsan

This comment has been minimized.

Copy link

javedahsan commented Mar 6, 2015

I am trying test buildhistory using above scripts. i can see request is triggered by sendEvent - is there way to troubleshoot the "buildhistory.coffee" script.

Thanks

@kalenwatermeyer

This comment has been minimized.

Copy link
Owner Author

kalenwatermeyer commented Mar 18, 2015

Hi @MrPeppa1,

I'm not 100% clear what you mean about splitting the display into two rows.

However, for better readability you can hide the job titles from the widget by setting the data-show_job_titles attribute in your HTML to false.

For example, in your dashboard.erb file:

<li data-row="1" data-col="1" data-sizex="1" data-sizey="1">
   <div data-id="buildhistory" data-view="Buildhistory" data-show_job_titles="false" data-max_samples="100"  data-sample_spacers="true"></div>
</li>
@kalenwatermeyer

This comment has been minimized.

Copy link
Owner Author

kalenwatermeyer commented Mar 18, 2015

Hi @javedahsan,

I use simple console.log statements in my CoffeeScript files to print out data to the browser console that is running my widget.
(Hit F12 in Chrome, Firefox or IE to display your browser's console)

I also use the JavaScript => CoffeeScript translator to check my CoffeeScript syntax (I am more familiar with JavaScript to be honest...)

To debug the data that is being received by your widget, make the following change to your buildhistory.coffee script:

class Dashing.Buildhistory extends Dashing.Widget
ready: ->
  # This is fired when the widget is done being rendered

onData: (data) ->
  # This is fired when the widget receives data


  # print out data to browser console 
  console.log data



  #extract widget paramaters
  @max_displayed_samples = @get('max_samples')
  @display_job_titles = @get('show_job_titles')
  @display_sample_spacers = @get('sample_spacers')

Hope this helps!

@smartideasinc

This comment has been minimized.

Copy link

smartideasinc commented Sep 4, 2016

Where to give the creditials of jenkins?
I am getting the following error

scheduler caught exception:
757: unexpected token at '<script>window.location.replace('/login?from=%2Fapi%2Fjson%3Ftree%3Djobs%5Bname%2Ccolor%5D');</script>

Authentication required

' /home/abhi/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/json/common.rb:155:in `parse' /home/abhi/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/json/common.rb:155:in`parse' /home/abhi/project/jobs/buildhistory.rb:172:in `block in ' /home/abhi/.rvm/gems/ruby-2.2.0/gems/rufus-scheduler-2.0.24/lib/rufus/sc/jobs.rb:230:in`call' /home/abhi/.rvm/gems/ruby-2.2.0/gems/rufus-scheduler-2.0.24/lib/rufus/sc/jobs.rb:230:in `trigger_block' /home/abhi/.rvm/gems/ruby-2.2.0/gems/rufus-scheduler-2.0.24/lib/rufus/sc/jobs.rb:204:in`block in trigger' /home/abhi/.rvm/gems/ruby-2.2.0/gems/rufus-scheduler-2.0.24/lib/rufus/sc/scheduler.rb:430:in `call' # /home/abhi/.rvm/gems/ruby-2.2.0/gems/rufus-scheduler-2.0.24/lib/rufus/sc/scheduler.rb:430:in `block in trigger_job'
@cwill00

This comment has been minimized.

Copy link

cwill00 commented Nov 17, 2017

Hi, perhaps a basic question....but I don't seem to get the widget to render. All backend works. Is there a specific verison of raphael I should be using?

@guysoft

This comment has been minimized.

Copy link

guysoft commented Feb 25, 2018

Why is this not in a git repo where it can be cloned and sorted better?
Should I make a repo and make @kalenwatermeyer its admin?

@totolanister

This comment has been minimized.

Copy link

totolanister commented Mar 5, 2020

Hi, perhaps a basic question....but I don't seem to get the widget to render. All backend works. Is there a specific verison of raphael I should be using?

I have the same problem, does anybody solve this problem?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.