Skip to content

Instantly share code, notes, and snippets.

@kalenwatermeyer
Last active July 29, 2019 03:39
Show Gist options
  • Select an option

  • Save kalenwatermeyer/9307200 to your computer and use it in GitHub Desktop.

Select an option

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
Copy Markdown

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

hicolour commented May 7, 2014

Copy link
Copy Markdown

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

@kalenwatermeyer

Copy link
Copy Markdown
Author

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

@kalenwatermeyer

Copy link
Copy Markdown
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
Copy Markdown

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

@kalenwatermeyer

Copy link
Copy Markdown
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
Copy Markdown

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
Copy Markdown

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
Copy Markdown

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