view planet.js @ 45:ff9b801f9dbd default tip

Added tag 2009-03-03-blog-post for changeset 7eec56d7c217
author Atul Varma <varmaa@toolness.com>
date Tue, 03 Mar 2009 16:58:52 -0800
parents b3508b131b16
children
line wrap: on
line source

/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Ubiquity.
 *
 * The Initial Developer of the Original Code is Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2007
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Atul Varma <atul@mozilla.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

// = Planet Ubiquity Code =
//
// This is the JavaScript source code for the Planet Ubiquity Redesign.
//
// == Workflow ==
//
// Each column on the Planet represents a feed. Each feed has a
// "feed processor" associated with it, which is in charge of taking
// the raw feed information and converting it into HTML content;
// during this time, it may also mash-up the feed with other
// information.
//
// However, the HTML generated by the feed processor is not its final
// representation. Once the feed processor is finished, we put each
// entry created by it into a "bin" depending on the date it was
// published. This allows us to create vertical rows that represent
// the region of time a set of entries originate from.
//
// Once this process is completed for all feeds, we do some final
// layout calculations and display the page.

// == Functions ==
//
// All functionality is contained in the {{{Planet}}} namespace.

var Planet = {
  // The base URL for where Ubiquity buildbot information is located.
  BUILDBOT_BASE: "http://ubiquity.mozilla.com/buildbot/",

  // The base URL for where Ubiquity's main HG repository is located.
  HG_BASE: "https://ubiquity.mozilla.com/hg/ubiquity-firefox/"
};

// === {{{Planet.getBuildInfo()}}} ===
//
// This function takes a DOM node or jQuery representing a buildbot
// Recent Builds HTML page (aka the "one line per build" page),
// scrapes it, and returns an array of objects containing information
// about the recent builds.

Planet.getBuildInfo = function getBuildInfo(page) {
  var builds = [];
  $("li", page).each(
    function(i) {
      var revRegexp = /.*rev=\[([0-9a-f]+)\].*/;
      builds.push({rev: $(this).text().match(revRegexp)[1],
                   href: (Planet.BUILDBOT_BASE +
                          $("a:not(:first)", this).attr("href")),
                   isSuccessful: $(".success", this).length > 0
                  });
    }
  );
  return builds;
};

// === {{{Planet.mashupBuildbotWithHgData()}}} ===
//
// This function takes a jQuery representing the HG log HTML and
// mashes it up with the DOM node or jQuery representing a Buildbot
// Recent Builds HTML page for the repository.

Planet.mashupBuildbotWithHgData = function mashup(hgLog, buildbotPage) {
  var builds = Planet.getBuildInfo(buildbotPage);
  builds.forEach(
    function(build) {
      var revUrl = Planet.HG_BASE + "rev/" + build.rev;
      var revSelector = ("a[href='" + revUrl + "']");
      var link = jQuery(revSelector, hgLog);
      var className = "build-errors";
      var title = "This build may have errors.";
      if (build.isSuccessful) {
        className = "build-success";
        title = "This build passed all tests.";
      }
      var status = jQuery('<span></span>');
      status.addClass(className);
      status.addClass("build");
      link.before(status);
      status.wrap('<a title="' + title +
                  '" href="' + build.href + '"></a>');
    });
};

// === {{{Planet.processHgFeed()}}} ===
//
// Feed processor to display recent HG commits mashed-up with Buildbot
// continuous-integration information about each revision.

Planet.processHgFeed = function processHgFeed(feed, content, cb) {
  Planet.processBlogFeed(
    feed,
    content,
    function() {
      jQuery.get(
        // This is just a Google Appengine app that allows us to pull
        // content from certain publicly-visible mozilla.com pages
        // via JSONP.
        ("http://about-mozilla.appspot.com/?url=" +
         Planet.BUILDBOT_BASE + "one_line_per_build&jsonp=?"),
        null,
        function(result) {
          Planet.mashupBuildbotWithHgData(content, $(result.data));
          cb();
        },
      "jsonp"
      );
    });
};

// === {{{Planet.processBlogFeed()}}} ===
//
// Generic feed processor to display a feed as though it represents a
// series of blog entries. It also performs some basic author
// filtering to remove email addresses and only include full names
// (this is done for the sake of readability, not spam prevention).

Planet.processBlogFeed = function processBlogFeed(feed, content, cb) {
  jQuery.each(
    feed.entries,
    function(i) {
      var item = $('<div class="blog-item">' +
                   '<span class="title"></span> ' +
                   '<span class="author"></span></div>');
      $('.title', item).html(this.title);
      var linkTitle = "";
      if (this.contentSnippet)
        linkTitle =this.contentSnippet ;
      var link = $('<a title="' + linkTitle + '" href="' +
                   this.link + '"></a>');
      link.attr("target", "_blank");
      $('.title', item).wrap(link);
      if (this.author) {
        var author = this.author;
        var authorFilters = [
            // Filter out e.g. "Robert Jones <robert@jones.com>"
            /(.+)\s*&lt;.+&gt;/,
            // Filter out e.g. "rob...@jones.com (Robert Jones)"
            /.+\.\.\.@.+\s+\((.+)\)/
        ];
        jQuery.each(
          authorFilters,
          function() {
            var match = author.match(this);
            if (match)
              author = match[1];
          });
        $('.author', item).html('by ' + author);
      }
      $(item).attr("published", this.publishedDate);
      content.append(item);
    });
  cb();
};

// === {{{Planet.doneLoadingFeeds()}}} ===
//
// Called when all feeds are done being loaded. At this point, we
// know exactly what will appear on the page, so we can do some
// final changes to the layout based on its content.

Planet.doneLoadingFeeds = function doneLoadingFeeds() {
  function fixHeights(timeAgo) {
    var tallestHeight = 0;
    var elements = $(".time-ago-" + timeAgo);
    elements.each(
      function(i) {
        var height = $(this).height();
        if (height > tallestHeight)
          tallestHeight = height;
      });
    elements.height(tallestHeight);
  }

  $("#issue").fadeIn();
  for (var i = 0; i < 10; i++)
    fixHeights(i);
  var columns = $("#issue .column");
  $(document.body).width(columns.outerWidth() * columns.length);
};

// === {{{Planet.splitByDate()}}} ===
//
// Takes the {{{published}}} attribute inserted in each entry by
// a feed processor and puts each entry into a separate "bin"
// based on its publish date.

Planet.splitByDate = function splitByDate(rawContent, content) {
  var now = new Date();
  $(".blog-item", rawContent).each(
    function(i) {
      var pub = new Date($(this).attr("published"));
      var msAgo = now - pub;
      var hoursAgo = msAgo / (1000 * 60 * 60);
      var timeAgo = Math.floor(hoursAgo / 8);
      if (timeAgo < 0)
        timeAgo = 0;
      if (timeAgo > 9)
        timeAgo = 9;
      var div = $(".time-ago-" + timeAgo, content);
      div.append(this);
    });
};

// === {{{Planet.showFeed()}}} ===
//
// Fetches content for a feed and renders it.

Planet.showFeed = function showFeed(feedInfo, cb) {
  var column = $('<div class="column"></div>');
  var headline = $('<div class="headline"></div>');
  headline.text(feedInfo.name);
  column.append(headline);
  $("#body").append(column);

  var feed = new google.feeds.Feed(feedInfo.url);
  feed.setNumEntries(feedInfo.entries);
  feed.includeHistoricalEntries();
  feed.load(
    function(result) {
      var rawContent = $('<div class="content"></div>');
      var content = $('<div class="content"></div>');

      for (var i = 0; i < 10; i++)
        content.append('<div class="time-ago-' + i + '"></div>');

      feedInfo.processFeed(
        result.feed,
        rawContent,
        function() {
          Planet.splitByDate(rawContent, content);
          column.append(content);
          cb();
        });
    });
};

// == The Feed Array ==
//
// This is an array of objects containing information about what
// feeds to display information for. Each object represents a
// column in the final rendered content.

Planet.FEEDS = [
  {name: "Blogs",
   url: "http://ubiquity.mozilla.com/planet/?feed=rss2",
   processFeed: Planet.processBlogFeed,
   entries: 10},
  {name: "Bugs",
   url: ("https://ubiquity.mozilla.com/trac/timeline?ticket=on" +
         "&milestone=on&wiki=on&max=50&daysback=90&format=rss"),
   processFeed: Planet.processBlogFeed,
   entries: 10},
  {name: "Code",
   url: Planet.HG_BASE + "rss-log",
   processFeed: Planet.processHgFeed,
   entries: 15},
  {name: "Discussions",
   url: ("http://groups.google.com/group/ubiquity-firefox/feed/" +
         "rss_v2_0_msgs.xml"),
   processFeed: Planet.processBlogFeed,
   entries: 30},
  {name: "Support",
   url: ("http://getsatisfaction.com/mozilla/products/mozilla_ubiquity.rss?" +
         "sort=recently_created"),
   processFeed: Planet.processBlogFeed,
   entries: 10}
];

// == Initialization ==
//
// Here we load the Google AJAX Feed API and commence the loading
// of our feeds.

google.load("feeds", "1");
google.setOnLoadCallback(
  function() {
    var feedsLeftToLoad = Planet.FEEDS.length;
    jQuery.each(
      Planet.FEEDS,
      function(i) {
        Planet.showFeed(this,
                        function() {
                          feedsLeftToLoad--;
                          if (!feedsLeftToLoad) {
                            Planet.doneLoadingFeeds();
                          }
                        });
      }
    );
  });