Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

View GitHub Commits and Issues

5.00/5 (9 votes)
7 May 2015CPOL3 min read 17.9K   92  
View GitHub commits, open and closed issues from this web page which can run directly from your local file system - no web server required
This is a pure JavaScript applet for which no server is required. It displays the recent Git commits (change log), open issues, and closed issues for a public GitHub project. It was written for a client so they could track progress and status on a website built for them (and which is a private repo).

Introduction

I decided to have a little fun with a pure JavaScript applet--no server required. This applet displays the recent:

  • Git commits (change log)
  • Open issues
  • Closed issues

for a public GitHub project. I ended up writing this for a client so they could track progress and status on a website I'm building for them (and which is a private repo). It turned out to be a fun little stand-alone HTML document.

Screenshots

The screenshots below are taken from the commits and issues for Joyent's node.js. You can put in any public repo name to view the logs, the applet (as you'll see in the JavaScript) defaults to this project.

Change Log

Here's a fragment showing the change log. Git returns the last 30 commits (not all shown here).

Image 1

I'm only really interested in two columns, but there's a wealth of information that can be added to the commit history.

Open Issues

Git also returns 30 open/closed issues.

Image 2

Again, there's more information that could be displayed.

Closed Issues

Obviously, identical (except for the content) to Open Issues. Notice the mouse-over tooltip box, available also in Open Issues.

Image 3

The Code

The entire applet resides in a single HTML file. We'll look at the various pieces next.

Third Party Scripts and Styles

Because it made life simpler, I'm using the following third party components:

  • jQuery
  • Knockout-js
  • Bootstrap
  • Moment-js
HTML
<script type="text/javascript" src="http://code.jquery.com/jquery-1.11.2.min.js"></script>
<script type="text/javascript" src="http://knockoutjs.com/downloads/knockout-3.2.0.js">
</script>
<script type="text/javascript" 
 src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
<script type="text/javascript" 
 src ="http://momentjs.com/downloads/moment-with-locales.min.js"></script>
<link rel="stylesheet" 
 href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
  • jQuery is used primarily for the AJAX calls.
  • Knockout is used for the minimal data binding.
  • Bootstrap is used primarily for the tabs.
  • Moment-js is used for formatting the date and time.

Styles

Not much needs to be said about the styles other than that I've made some minor attempts at improving the spacing of controls and columns and color-banded the rows:

CSS
<style>
  .log tr:nth-child(even) {background: rgb(230, 255, 230)}
  .log tr:nth-child(odd) {background: #FFF}
  input {padding-left: 5px}
  .container {padding-top: 20px; padding-bottom: 20px; margin-left: 10px;}
  .datefield {padding-left:20px; width:170px}
  .nav-tabs, .tab-content {padding-left:20px}
  .data-list th {padding-left:20px}
  .data-list td {padding-left:20px}
  .data-list td:last-child {padding-right:20px}
  #gitRepoUrl {width:25%}
  #btnGo {padding-left:10px}
</style>

JavaScript

Document Ready

Here, we create a global variable for the Knockout view-model, initialize it, and wait for a click on the Go button:

JavaScript
 var viewModel

$(document).ready(function () {
  viewModel = dataBind()
  $('#btnGo').on('click', function() {
    getCommits()
    getOpenIssues()
    getClosedIssues()
  })
})

Initializing the View Model

The view model is really quite simple, consisting of databinding the URL input and the three tables:

JavaScript
function dataBind() {
  var vm = new function() { 
    this.gitRepoUrl = ko.observable("joyent/node")
    this.commits = ko.observable([])
    this.openIssues = ko.observable([])
    this.closedIssues = ko.observable([])
  }
  ko.applyBindings(vm);
  return vm
}

Get Commits and Issues

When the "Go" button is clicked, we issue three separate AJAX requests to GitHub:

JavaScript
function getCommits() {
  $.ajax({
    dataType: 'json',
    url: 'https://api.github.com/repos/' + viewModel.gitRepoUrl() + '/commits',
    success: showCommits,
    error: function() {
      viewModel.commits([])
      viewModel.openIssues([])
      viewModel.closedIssues([])
      alert("Unable to connect to repo '" + viewModel.gitRepoUrl() +"'")
    }
  })
}

Note that an error is displayed only for getting commits -- if getting commits fails, the issue gets are expected to fail as well, so they don't have a specific error handler.

JavaScript
function getOpenIssues() {
  $.ajax({
    dataType: 'json',
    url: 'https://api.github.com/repos/' + viewModel.gitRepoUrl() + '/issues?state=open',
    success: showOpenIssues
  })
}

function getClosedIssues() {
  $.ajax({
    dataType: 'json',
    url: 'https://api.github.com/repos/' + viewModel.gitRepoUrl() + '/issues?state=closed',
    success: showClosedIssues
  })
}

Updating the View-Model

For each successful AJAX response, the view-model is updated, which updates the page:

JavaScript
function showCommits(data) {
  viewModel.commits(data)
  formatDates('.commit-datefield')
}

function showOpenIssues(data) {
  viewModel.openIssues(data)
  formatDates('.open-issues-datefield')
}

function showClosedIssues(data) {
  viewModel.closedIssues(data)
  formatDates('.closed-issues-datefield')
}

Format Date/Time

The date/time is formatted to the 'en' local standard:

JavaScript
function formatDates(classId) {
  moment.locale('en')
  $(classId).each(function (index, dateElem) {
    var formatted = moment(dateElem.textContent).format('lll')
    dateElem.textContent = formatted;
  })
}

The HTML

Last but not least is the HTML:

HTML
<div class="container">
  <label>Git Repo Name:</label>
  <input type='text' id='gitRepoUrl' data-bind='value: gitRepoUrl'/>
  <input type='button' id='btnGo' value='Go'/>
</div>
<div id="content">
  <ul id="tabs" class="nav nav-tabs" data-tabs="tabs">
    <li class="active"><a href="#changeLog" data-toggle="tab">Change Log</a></li>
    <li><a href="#openIssues" data-toggle="tab">Open Issues</a></li>
    <li><a href="#closedIssues" data-toggle="tab">Closed Issues</a></li>
  </ul> 
  <div id="my-tab-content" class="tab-content">
    <div class="tab-pane active" id="changeLog"> 
      <h1>Change Log</h1>
      <table class="data-list">
        <thead>
          <tr>
            <th>Date</th>
            <th>Change</th>
          </tr>
        </thead>
        <tbody class='log' data-bind="foreach: commits">
          <tr> 
            <td class='commit-datefield datefield' data-bind="text: commit.author.date"></td>
            <td data-bind="text: commit.message"></td>
          </tr>
        </tbody>
      </table>
    </div>
    <div class="tab-pane" id="openIssues">
    <h1>Open Issues</h1>
      <table class="data-list">
        <thead>
          <tr>
            <th>Number</th>
            <th>Date</th>
            <th>Issue</th>
          </tr>
        </thead>
        <tbody class='log' data-bind="foreach: openIssues">
          <tr>
            <td data-bind="text: number"></td>
            <td class='open-issues-datefield datefield' data-bind="text: created_at"></td>
            <td data-bind="text: title, attr: { title: body }"></td>
          </tr>
        </tbody>
      </table>
    </div>
    <div class="tab-pane" id="closedIssues">
      <h1>Closed Issues</h1>
      <table class="data-list">
        <thead>
          <tr>
            <th>Number</th>
            <th>Date</th>
            <th>Issue</th>
          </tr>
        </thead>
        <tbody class='log' data-bind="foreach: closedIssues">
          <tr>
            <td data-bind="text: number"></td>
            <td class='closed-issues-datefield datefield' data-bind="text: created_at"></td>
            <td data-bind="text: title, attr: { title: body }"></td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</div>

If You Need Authentication

If you're accessing a private repo, you can pass in your authentication access token as an Authorization header, for example:

JavaScript
function getCommits() {
  $.ajax({
    dataType: 'json',
    url: 'https://api.github.com/repos/' + viewModel.gitRepoUrl() + '/commits',
    headers: {
      "Authorization": "token " + token
    },
    success: showCommits
  })
}

The problem with this approach is that anyone can access your token through the browser's debugging facilities. In other words, security should not be handled by JavaScript - you certainly don't want to hardcode your token, but rather obtain it from the server. Technically, if the ability to view commits and issues is itself an authorized route, you at least know that the people viewing those pages are authorized to do so, but this is probably a weak defence. Therefore, at the end of the day, for private repos that require an access token, you should probably revert to a client-server hybrid, in which the server passes down the pre-acquired commits and issues.

Conclusion

This was a fun little applet to put together and I hope that it might be of some use to others!

History

  • 7th May, 2015: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)