Skip to content

Instantly share code, notes, and snippets.

@kalenwatermeyer
Last active July 29, 2019 03:39
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save kalenwatermeyer/9307200 to your computer and use it in GitHub Desktop.
Save kalenwatermeyer/9307200 to your computer and use it in GitHub Desktop.
Jenkins - Last commit info widget
require 'net/http'
require 'json'
require 'time'
############################################################
#widget constants
############################################################
#Jenkins server base URL
JENKINS_URI = URI.parse("http://YOUR_JENKINS_URL:8080")
#change to true if Jenkins is using SSL
JENKINS_USING_SSL = false
#credentials of Jenkins user (give these values if the above flag is true)
JENKINS_AUTH = {
'name' => nil,
'password' => nil
}
#Hash of all Jenkins jobs to be monitored for SCM changes, mapped to their event IDs
#Add your Jenkins jobs & their associated unique event IDs here
$jenkins_jobs_to_be_monitored = {
'My first Jenkins job' => 'jenkins-last-commit',
'My second Jenkins job' => 'jenkins-last-commit-2'
}
#Trim thresholds (widget display)
COMMIT_MESSAGE_TRIM_LENGTH = 120
FILE_LIST_TRIM_LENGTH = 4
MAX_FILENAME_LENGTH = 100
FILENAME_TAIL_LENGTH = 30
#helper function that trims file names
#for long filenames, this function keeps all chars up to the
#trim length, inserts an elipsis and then keeps the "tail" of the file name
# My_extra_long_file_name.cpp => My_extra...file_name.cpp
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
#helper function which fetches JSON for job changeset
def get_json_for_job_changeset(job_name, build = 'lastBuild')
begin
job_name = URI.encode(job_name)
http = Net::HTTP.new(JENKINS_URI.host, JENKINS_URI.port)
request = Net::HTTP::Get.new("/job/#{job_name}/#{build}/api/json?tree=changeSet[*[*[*]]]")
#check if Jenkins is implementing SSL
if JENKINS_USING_SSL == true
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
if JENKINS_AUTH['name']
request.basic_auth(JENKINS_AUTH['name'], JENKINS_AUTH['password'])
end
response = http.request(request)
JSON.parse(response.body)
rescue Exception => e
#exception encountered - raise exception
raise e
end
end
#scheduled job
SCHEDULER.every '60s' do
begin
#iterate over all Jenkins jobs in the hash
$jenkins_jobs_to_be_monitored.each do |jenkins_job_name, widget_event_id|
#fetch changeset information for selected job
commit_info = get_json_for_job_changeset(jenkins_job_name)
commit_items = commit_info["items"]
# check if the "items" array item contains elements
# Note: "items" may be empty, for example when Jenkins is in the process of building the
# job which is being monitored for changes
if !commit_info["changeSet"]["items"].empty?
#check if we're dealing with git
if commit_info["changeSet"]["kind"] == 'git'
commit_id = commit_info["changeSet"]["items"][0]["commitId"]
commit_date = commit_info["changeSet"]["items"][0]["date"]
else
#not using git - fall back to Perforce JSON structure
commit_id = commit_info["changeSet"]["items"][0]["changeNumber"]
commit_date = commit_info["changeSet"]["items"][0]["changeTime"]
end
#extract commit information fields from Jenkins JSON response
author_name = commit_info["changeSet"]["items"][0]["author"]["fullName"]
#process commit message
commit_message = commit_info["changeSet"]["items"][0]["msg"]
#trim message length if necessary
if commit_message.length > COMMIT_MESSAGE_TRIM_LENGTH
commit_message = commit_message.to_s[0..COMMIT_MESSAGE_TRIM_LENGTH].gsub(/[^\w]\w+\s*$/, ' ...')
end
#build up list of affected files
file_items = commit_info["changeSet"]["items"][0]["affectedPaths"]
affected_items = Array.new
#add key-value pair for each file found
file_items.sort.each { |x| affected_items.push( {:file_name => trim_filename(x)} ) }
#trim file list length if necessary
if affected_items.length > FILE_LIST_TRIM_LENGTH
length = affected_items.length
affected_items = affected_items.slice(0, FILE_LIST_TRIM_LENGTH)
#add indication of total number of affected files
affected_items[FILE_LIST_TRIM_LENGTH] = {:file_name => ' ... (' + length.to_s + ' files in total)'}
end
json_formatted_data = Hash.new
json_formatted_data[0] = { id: commit_id, timestamp: commit_date, message: commit_message, author: author_name }
#send event to dashboard
send_event(widget_event_id, commit_entries: json_formatted_data.values, commit_files: affected_items)
else
#Jenkins is busy
print "[Fetching changeSet from Jenkins] JSON object doesn't contain data ... \n"
end
end
rescue Exception => e
#exception encountered
print "Oops! exception encountered!\n"
print e.message
print "\n"
end
end
class Dashing.Jenkinslastcommit extends Dashing.Widget
<h1 class="title" data-bind="title"></h1>
<ul class="list-nostyle">
<li data-foreach-commit_entries="commit_entries">
<span class="timestamp" data-bind="commit_entries.timestamp"></span>
<span class="author" data-bind="commit_entries.author"></span>
<p class="commit_id" data-bind="commit_entries.id"></p>
<p class="commit_message" data-bind="commit_entries.message"></p>
</li>
</ul>
<ul class="list-no-style">
<li data-foreach-commit_files="commit_files">
<p class="commit_file" data-bind="commit_files.file_name"></p>
</li>
</ul>
<p class="updated-at" data-bind="updatedAtMessage"></p>

Jenkins Last Commit - screenshot

Description

The Jenkins Last Commit widget periodically fetches SCM change information from a Jenkins instance for a specified list of jobs.

This allows you to have multiple widgets simultaneously (monitoing separate jobs for changes).

Calls are made to the Jenkins API to retrieve the changeset object in a JSON form. This JSON object contains various pieces of information about the last commit made for the Jenkins job.

This widget uses the Jenkins Build as a base, and extends the native List Dashing widget.

The widget has been tested with Git and Perforce changesets on Jenkins. The widget has also been tested with secure Jenkins instances (SSL / HTTPS).

Installing the widget

Place the following files in your widgets/jenkinslastcommit folder:

  • jenkinslastcommit.coffee
  • jenkinslastcommit.html
  • jenkinslastcommit.scss

Place the following file in your jobs/ folder:

  • jenkins-last-commit.rb

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 jenkins-last-commit.rb file, modify the following parameters according to your needs:

Parameter Meaning
JENKINS_URI Jenkins base URL
JENKINS_USING_SSL true if using SSL (HTTPS), false if not using SSL (HTTP)
JENKINS_AUTH Jenkins user credentials (required for HTTPS implementations)
$jenkins_jobs_to_be_monitored This is a hash containing multiple key-value pairs, where: key = Jenkins job to monitor, Value = dashboard event ID

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:

 <!-- widget with event ID: jenkins-last-commit-1 -->
 <li data-row="1" data-col="1" data-sizex="2" data-sizey="1">
      <div data-id="jenkins-last-commit-1" data-view="Jenkinslastcommit" data-unordered="true" data-title="Last commit (job 1)" ></div>
 </li>
 
 <!-- widget with event ID: jenkins-last-commit-2 -->
 <li data-row="1" data-col="1" data-sizex="2" data-sizey="1">
      <div data-id="jenkins-last-commit-2" data-view="Jenkinslastcommit" data-unordered="true" data-title="Last commit (job 2)" ></div>
 </li>

Differences between JSON structures for different SCMs

Jenkins provides slightly different JSON structures for the changeSet objects between Git and Perforce - there may be differences in the JSON structure when used with your SCM.

JSON structure for changeSet: Perforce

{
  "changeSet": {
    "items": [
      {
        "affectedPaths": [
          "..../XXX.cpp",
          ".../YYY.cpp"
        ],
        "author": {
          "absoluteUrl": "http://JENKINS/user/USERNAME",
          "description": null,
          "fullName": "USERNAME",
          "id": "USERNAME",
          "property": [
            { },{ },{ },{ },{ },{ },
          ]
        },
        "commitId": null,
        "msg": "Commit message",
        "timestamp": -1,
        "changeNumber": "20548",
        "changeTime": "2014-02-27 19:24:52"
      }      
    ],
    "kind": null
  }
}

JSON structure for changeSet: Git

{
  "changeSet"=>{
    "items"=>[
      {
        "affectedPaths"=>[
          ".../XXX.cpp",
          ".../YYY.cpp"
        ],
        "commitId"=>"4429435e1eb546b745775ea957ede9f2983df6b9",
        "timestamp"=>1393760162000,
        "author"=>{
          "absoluteUrl"=>"http://JENKINS/USERNAME",
          "description"=>nil,
          "fullName"=>"USERNAME",
          "id"=>"USERNAME",
          "property"=>[ {}, {}, {}, {}, {}, {} 
          ]
        },
        "comment"=>"Commit message text",
        "date"=>"2014-03-02 13:36:02 +0200",
        "id"=>"4429435e1eb546b745775ea957ede9f2983df6b9",
        "msg"=>"Commit message text",
        "paths"=>[
          {
            "editType"=>"edit",
            "file"=>".../XXX.cpp"
          },
          {
            "editType"=>"edit",
            "file"=>".../YYY.cpp"
          }
        ]
      }
    ],
    "kind"=>"git"
  }
}
// ----------------------------------------------------------------------------
// Sass declarations
// ----------------------------------------------------------------------------
$background-color: #12b0c5;
$title-color: rgba(255, 255, 255, 0.7);
$commit_message-color: #fff;
$commit_id-color: rgba(0, 0, 0, 0.6);
$moreinfo-color: rgba(255, 255, 255, 0.7);
$timestamp-color: rgba(255, 255, 255, 0.8);
$author-color: rgba(255, 255, 255, 0.9);
$commit_file_color: rgba(0, 0, 0, 0.5);
// ----------------------------------------------------------------------------
// Widget-jenkinslastcommit styles
// ----------------------------------------------------------------------------
.widget-jenkinslastcommit {
background-color: $background-color;
vertical-align: top;
.title {
color: $title-color;
}
ol, ul {
margin: 0 15px;
text-align: left;
}
ol {
list-style-position: inside;
}
li {
margin-bottom: 5px;
}
.list-nostyle {
list-style: none;
}
.commit_id {
clear: both;
margin-left: 12px;
padding-bottom: 10px;
color: $commit_id-color;
font-size: 12px;
font-weight: 600;
}
.commit_message {
margin-left: 12px;
font-weight: 600;
color: $commit_message-color;
}
.timestamp {
float: left;
margin-left: 12px;
font-weight: 600;
color: $timestamp-color;
font-size: 14px;
}
.author {
float: right;
margin-right: 12px;
font-weight: 600;
color: $author-color;
font-size: 14px;
}
.commit_file {
margin-left: 50px;
color: $commit_file_color;
font-size: 10px;
font-weight: 600;
}
.updated-at {
color: rgba(0, 0, 0, 0.3);
}
.more-info {
color: $moreinfo-color;
}
}
@larrycai
Copy link

Excellent, easy to install and use.

In "Installing the widget" section
Place the following files in your widgets/ folder:
=>
Place the following files in your widgets/jenkinslastcommit folder:

@hicolour
Copy link

hicolour commented May 7, 2014

It would be nice if we could monitor more then one job, like it is in jenkins ci widget ;)

@kalenwatermeyer
Copy link
Author

Hi @larrycai - thanks for the feedback. I've updated the installation instructions.

@kalenwatermeyer
Copy link
Author

Good suggestion @hicolour - I'll take a look at monitoring implementing multiple jobs with a mapping style just like the Jenkins CI widget

@iancrowther
Copy link

+1 for the multiple jobs, just pulled this from our dashboard which is a shame

@kalenwatermeyer
Copy link
Author

@hicolour, @iancrowther - I've updated the widget to allow monitoring of multiple Jenkins jobs simultaneously. As with the Jenkins CI widget, you can specify a collection of Jenkins jobs to monitor.

@adrianRodriguez123
Copy link

Hi guys,

I don't know why, but in my widget only appears the title, nothing more. I don't have any error message. Can I put the token instead of the password?. Could you help me please?

@aditeedesale
Copy link

Could you please tell us how we can fetch information about last 10 commits(or changes) in only one jenkins job into the dashing ,like the commit massege , auther name, build number+svnRevisionNumber ,time ?

@ScottBrenner
Copy link

Had to change the scheduled job slightly to match Jenkins ver. 2.121.2

SCHEDULER.every '60s', :first_in => 0 do
  begin
    #iterate over all Jenkins jobs in the hash
    $jenkins_jobs_to_be_monitored.each do |jenkins_job_name, widget_event_id|
    
      #fetch changeset information for selected job
      commit_info = get_json_for_job_changeset(jenkins_job_name)
      commit_items = commit_info["changeSets"][0]["items"]
    
      # check if the "items" array item contains elements
      # Note: "items" may be empty, for example when Jenkins is in the process of building the 
      # job which is being monitored for changes
      if !commit_info["changeSets"][0]["items"].empty?
       
        #check if we're dealing with git
        if commit_info["changeSets"][0]["kind"] == 'git'
          commit_id = commit_info["changeSets"][0]["items"][0]["commitId"]
          commit_date = commit_info["changeSets"][0]["items"][0]["date"]
        else
          #not using git - fall back to Perforce JSON structure
          commit_id = commit_info["changeSets"][0]["items"][0]["changeNumber"]
          commit_date = commit_info["changeSets"][0]["items"][0]["changeTime"]
        end
        
        #extract commit information fields from Jenkins JSON response
        author_name = commit_info["changeSets"][0]["items"][0]["author"]["fullName"]
        
        #process commit message
        commit_message = commit_info["changeSets"][0]["items"][0]["msg"]

        #trim message length if necessary
        if commit_message.length > COMMIT_MESSAGE_TRIM_LENGTH
          commit_message = commit_message.to_s[0..COMMIT_MESSAGE_TRIM_LENGTH].gsub(/[^\w]\w+\s*$/, ' ...')
        end
        
        #build up list of affected files
        file_items = commit_info["changeSets"][0]["items"][0]["affectedPaths"]
        affected_items = Array.new
        
        #add key-value pair for each file found
        file_items.sort.each { |x| affected_items.push( {:file_name => trim_filename(x)} ) }
        
        #trim file list length if necessary
        if affected_items.length > FILE_LIST_TRIM_LENGTH
          length = affected_items.length
          affected_items = affected_items.slice(0, FILE_LIST_TRIM_LENGTH)
          
          #add indication of total number of affected files
          affected_items[FILE_LIST_TRIM_LENGTH] = {:file_name => '  ...  (' + length.to_s + ' files in total)'}
        end
        
        json_formatted_data = Hash.new
        json_formatted_data[0] = { id: commit_id, timestamp: commit_date, message: commit_message, author: author_name }
        
        #send event to dashboard
        send_event(widget_event_id, commit_entries: json_formatted_data.values, commit_files: affected_items)
     
      else
        #Jenkins is busy
        print "[Fetching changeSets from Jenkins] JSON object doesn't contain data ... \n"
      end    
    end
    
  rescue Exception => e
    #exception encountered 
    print "Oops! exception encountered!\n"
    print e.message
    print "\n"
  end
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment