dash-mario 0.16 → 0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +11 -0
 - data/README.rdoc +22 -15
 - data/dash-mario.gemspec +1 -1
 - data/lib/dash-fu/mario.rb +56 -5
 - data/lib/dash-fu/marios/backtweets.rb +17 -25
 - data/lib/dash-fu/marios/github.rb +55 -62
 - data/lib/dash-fu/marios/github_issues.rb +43 -44
 - data/lib/dash-fu/marios/ruby_gems.rb +28 -29
 - data/test/backtweets_test.rb +53 -57
 - data/test/cassettes/backtweets.yml +17 -5
 - data/test/github_issues_test.rb +82 -82
 - data/test/github_test.rb +95 -94
 - data/test/helpers/source.rb +36 -20
 - data/test/ruby_gems_test.rb +11 -11
 - data/test/setup.rb +1 -1
 - data/test/test.log +18 -0
 - metadata +4 -4
 
    
        data/CHANGELOG
    CHANGED
    
    | 
         @@ -1,3 +1,14 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            2010-09-12 v0.17 API change to callback and HTTP access
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Changed API to use callback object instead of block with too many argument
         
     | 
| 
      
 4 
     | 
    
         
            +
            combinations.
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            Clarified and validating that source state can only use alphanumeric/underscore
         
     | 
| 
      
 7 
     | 
    
         
            +
            in field names.
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            Added wrapper around HTTP API to make it easier to switch to asynchronous
         
     | 
| 
      
 10 
     | 
    
         
            +
            processing later on.
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
       1 
12 
     | 
    
         
             
            2010-09-08 v0.16 To name a source, set source.name
         
     | 
| 
       2 
13 
     | 
    
         | 
| 
       3 
14 
     | 
    
         
             
            During setup, name the source by setting source.name, not metric.name.
         
     | 
    
        data/README.rdoc
    CHANGED
    
    | 
         @@ -41,6 +41,11 @@ For metric.columns, each item can use the following keys: 
     | 
|
| 
       41 
41 
     | 
    
         
             
            * id -- Column identifier (if missing, derived from column name)
         
     | 
| 
       42 
42 
     | 
    
         
             
            * name -- Column name (required)
         
     | 
| 
       43 
43 
     | 
    
         | 
| 
      
 44 
     | 
    
         
            +
            Note: any state information you want to store can only use field names with
         
     | 
| 
      
 45 
     | 
    
         
            +
            alphanumeric charactercs and underscore. Periods and all other special
         
     | 
| 
      
 46 
     | 
    
         
            +
            characters are reserved for special field names that are not stored as part of
         
     | 
| 
      
 47 
     | 
    
         
            +
            the state.
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
       44 
49 
     | 
    
         
             
            Shortly after setup, the validate method is called with the same context.  If it
         
     | 
| 
       45 
50 
     | 
    
         
             
            raises any exception, the error is displayed to the user and the source is
         
     | 
| 
       46 
51 
     | 
    
         
             
            discarded. Otherwise, register is called with a Webhook URL.  Some Marios use
         
     | 
| 
         @@ -50,25 +55,27 @@ that to register the Webhook with another service. 
     | 
|
| 
       50 
55 
     | 
    
         
             
            == Updates
         
     | 
| 
       51 
56 
     | 
    
         | 
| 
       52 
57 
     | 
    
         
             
            Periodically, the Mario will be asked to update the source by calling the update
         
     | 
| 
       53 
     | 
    
         
            -
            method. This method is called with the same context and  
     | 
| 
       54 
     | 
    
         
            -
            from a webhook, then the second argument to update is a Rack::Request 
     | 
| 
      
 58 
     | 
    
         
            +
            method. This method is called with the same context and callbacks.  If data
         
     | 
| 
      
 59 
     | 
    
         
            +
            comes from a webhook, then the second argument to update is a Rack::Request
         
     | 
| 
      
 60 
     | 
    
         
            +
            object.
         
     | 
| 
       55 
61 
     | 
    
         | 
| 
       56 
     | 
    
         
            -
            The  
     | 
| 
       57 
     | 
    
         
            -
             
     | 
| 
       58 
     | 
    
         
            -
             
     | 
| 
      
 62 
     | 
    
         
            +
            The callback object passed to update can be used to update the source by calling
         
     | 
| 
      
 63 
     | 
    
         
            +
            one of its many methods.  A single update may call any combination of methods,
         
     | 
| 
      
 64 
     | 
    
         
            +
            e.g. increasing a metric and recording an activity.
         
     | 
| 
       59 
65 
     | 
    
         | 
| 
       60 
     | 
    
         
            -
            The  
     | 
| 
      
 66 
     | 
    
         
            +
            The callback methods exposed by the callback object are:
         
     | 
| 
       61 
67 
     | 
    
         | 
| 
       62 
     | 
    
         
            -
            * set -- Columns to set (metric). Records the most recent value for this
         
     | 
| 
       63 
     | 
    
         
            -
              metric.  
     | 
| 
       64 
     | 
    
         
            -
              are integers or floats.
         
     | 
| 
       65 
     | 
    
         
            -
            * inc -- Columns to increment (metric). Records a change in value which may
         
     | 
| 
       66 
     | 
    
         
            -
              be positive or negative. This is a hash where the keys are column ids (or
         
     | 
| 
      
 68 
     | 
    
         
            +
            * set! -- Columns to set (metric). Records the most recent value for this
         
     | 
| 
      
 69 
     | 
    
         
            +
              metric. The single argument is a hash where the keys are column ids (or
         
     | 
| 
       67 
70 
     | 
    
         
             
              indexes), the values are integers or floats.
         
     | 
| 
       68 
     | 
    
         
            -
            *  
     | 
| 
       69 
     | 
    
         
            -
             
     | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
     | 
    
         
            -
             
     | 
| 
      
 71 
     | 
    
         
            +
            * inc! -- Columns to increment (metric). Records a change in value which may
         
     | 
| 
      
 72 
     | 
    
         
            +
              be positive or negative. The single argument is a hash where the keys are
         
     | 
| 
      
 73 
     | 
    
         
            +
              column ids (or indexes), the values are integers or floats.
         
     | 
| 
      
 74 
     | 
    
         
            +
            * activity! -- Records a single activity. The single argument is a hash with
         
     | 
| 
      
 75 
     | 
    
         
            +
              various values described below.
         
     | 
| 
      
 76 
     | 
    
         
            +
            * error! -- Records a processing error. The single agrument is an error message.
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
            An activity is specified using the following fields:
         
     | 
| 
       72 
79 
     | 
    
         | 
| 
       73 
80 
     | 
    
         
             
            * uid -- Unique identifier (within the scope of this source). For example,
         
     | 
| 
       74 
81 
     | 
    
         
             
              if activity is a release, this could be the version number.
         
     | 
    
        data/dash-mario.gemspec
    CHANGED
    
    
    
        data/lib/dash-fu/mario.rb
    CHANGED
    
    | 
         @@ -99,11 +99,11 @@ module DashFu 
     | 
|
| 
       99 
99 
     | 
    
         
             
                end
         
     | 
| 
       100 
100 
     | 
    
         | 
| 
       101 
101 
     | 
    
         
             
                # Called to update the source. This method will be called periodically with
         
     | 
| 
       102 
     | 
    
         
            -
                # a source and a  
     | 
| 
       103 
     | 
    
         
            -
                # source, Rack::Request and a block. It can  
     | 
| 
       104 
     | 
    
         
            -
                # times with any combination of the supported named arguments for 
     | 
| 
       105 
     | 
    
         
            -
                # the source.
         
     | 
| 
       106 
     | 
    
         
            -
                def update(source, request,  
     | 
| 
      
 102 
     | 
    
         
            +
                # a source and a callback. When triggered by a Webhook, it will be called
         
     | 
| 
      
 103 
     | 
    
         
            +
                # with source, Rack::Request and a block. It can invoke callback methods any
         
     | 
| 
      
 104 
     | 
    
         
            +
                # number of times with any combination of the supported named arguments for
         
     | 
| 
      
 105 
     | 
    
         
            +
                # updating the source.
         
     | 
| 
      
 106 
     | 
    
         
            +
                def update(source, request, callbacks)
         
     | 
| 
       107 
107 
     | 
    
         
             
                end
         
     | 
| 
       108 
108 
     | 
    
         | 
| 
       109 
109 
     | 
    
         
             
                # Called to unregister a Webhook (for sources that don't need it).
         
     | 
| 
         @@ -118,6 +118,57 @@ module DashFu 
     | 
|
| 
       118 
118 
     | 
    
         
             
                  []
         
     | 
| 
       119 
119 
     | 
    
         
             
                end
         
     | 
| 
       120 
120 
     | 
    
         | 
| 
      
 121 
     | 
    
         
            +
                # Use this to make HTTP request to external services. This method opens a
         
     | 
| 
      
 122 
     | 
    
         
            +
                # new session and yields to the block. The block can them make HTTP requests
         
     | 
| 
      
 123 
     | 
    
         
            +
                # on the remote host. The block may be called asynchronously.
         
     | 
| 
      
 124 
     | 
    
         
            +
                def session(host, port = 80)
         
     | 
| 
      
 125 
     | 
    
         
            +
                  http = Net::HTTP.new(host, port)
         
     | 
| 
      
 126 
     | 
    
         
            +
                  http.use_ssl = true if port == 443
         
     | 
| 
      
 127 
     | 
    
         
            +
                  http.start do |http|
         
     | 
| 
      
 128 
     | 
    
         
            +
                    yield Session.new(http)
         
     | 
| 
      
 129 
     | 
    
         
            +
                  end
         
     | 
| 
      
 130 
     | 
    
         
            +
                end
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
                # HTTP Session.
         
     | 
| 
      
 133 
     | 
    
         
            +
                class Session
         
     | 
| 
      
 134 
     | 
    
         
            +
                  def initialize(http) #:nodoc:
         
     | 
| 
      
 135 
     | 
    
         
            +
                    @http = http
         
     | 
| 
      
 136 
     | 
    
         
            +
                  end
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
                  # Make a GET request and yield response to the block. Response consists of
         
     | 
| 
      
 139 
     | 
    
         
            +
                  # three arguments: status code, response body and response headers. The
         
     | 
| 
      
 140 
     | 
    
         
            +
                  # block may be called asynchronoulsy.
         
     | 
| 
      
 141 
     | 
    
         
            +
                  def get(path, headers = {}, &block)
         
     | 
| 
      
 142 
     | 
    
         
            +
                    response = @http.request(get_request(path, headers))
         
     | 
| 
      
 143 
     | 
    
         
            +
                    yield response.code, response.body, {}
         
     | 
| 
      
 144 
     | 
    
         
            +
                  end
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
                  # Make a GET request and yield response to the block. If the response
         
     | 
| 
      
 147 
     | 
    
         
            +
                  # status is 200 the second argument is the response JSON object. The block
         
     | 
| 
      
 148 
     | 
    
         
            +
                  # may be called asynchronously.
         
     | 
| 
      
 149 
     | 
    
         
            +
                  def get_json(path, headers = {}, &block)
         
     | 
| 
      
 150 
     | 
    
         
            +
                    response = @http.request(get_request(path, headers))
         
     | 
| 
      
 151 
     | 
    
         
            +
                    if Net::HTTPOK === response
         
     | 
| 
      
 152 
     | 
    
         
            +
                      json = JSON.parse(response.body) rescue nil
         
     | 
| 
      
 153 
     | 
    
         
            +
                      if json
         
     | 
| 
      
 154 
     | 
    
         
            +
                        yield response.code.to_i, json, {}
         
     | 
| 
      
 155 
     | 
    
         
            +
                      else
         
     | 
| 
      
 156 
     | 
    
         
            +
                        yield 500, "Not a JSON document", {}
         
     | 
| 
      
 157 
     | 
    
         
            +
                      end
         
     | 
| 
      
 158 
     | 
    
         
            +
                    else
         
     | 
| 
      
 159 
     | 
    
         
            +
                      yield response.code.to_i, response.message, {}
         
     | 
| 
      
 160 
     | 
    
         
            +
                    end
         
     | 
| 
      
 161 
     | 
    
         
            +
                  end
         
     | 
| 
      
 162 
     | 
    
         
            +
             
     | 
| 
      
 163 
     | 
    
         
            +
                protected
         
     | 
| 
      
 164 
     | 
    
         
            +
             
     | 
| 
      
 165 
     | 
    
         
            +
                  def get_request(path, headers)
         
     | 
| 
      
 166 
     | 
    
         
            +
                    request = Net::HTTP::Get.new(path)
         
     | 
| 
      
 167 
     | 
    
         
            +
                    request.basic_auth headers[:username], headers[:password] if headers[:username] && headers[:password]
         
     | 
| 
      
 168 
     | 
    
         
            +
                    request
         
     | 
| 
      
 169 
     | 
    
         
            +
                  end
         
     | 
| 
      
 170 
     | 
    
         
            +
                end
         
     | 
| 
      
 171 
     | 
    
         
            +
             
     | 
| 
       121 
172 
     | 
    
         
             
              protected
         
     | 
| 
       122 
173 
     | 
    
         | 
| 
       123 
174 
     | 
    
         
             
                # Source identifier.
         
     | 
| 
         @@ -6,7 +6,7 @@ module DashFu::Mario 
     | 
|
| 
       6 
6 
     | 
    
         
             
                def setup(source, params)
         
     | 
| 
       7 
7 
     | 
    
         
             
                  url = params["url"].strip.downcase.sub(/^http(s?):\/\//, "")
         
     | 
| 
       8 
8 
     | 
    
         
             
                  source["source.name"] = "Tweets for #{url}"
         
     | 
| 
       9 
     | 
    
         
            -
                  source["metric.columns"] = [{ : 
     | 
| 
      
 9 
     | 
    
         
            +
                  source["metric.columns"] = [{ id: "tweets", label: "Tweets" }]
         
     | 
| 
       10 
10 
     | 
    
         
             
                  source["metric.totals"] = true
         
     | 
| 
       11 
11 
     | 
    
         
             
                  source["url"] = url
         
     | 
| 
       12 
12 
     | 
    
         
             
                end
         
     | 
| 
         @@ -15,43 +15,35 @@ module DashFu::Mario 
     | 
|
| 
       15 
15 
     | 
    
         
             
                  raise "Missing URL" if source["url"].blank?
         
     | 
| 
       16 
16 
     | 
    
         
             
                end
         
     | 
| 
       17 
17 
     | 
    
         | 
| 
       18 
     | 
    
         
            -
                def update(source, request,  
     | 
| 
       19 
     | 
    
         
            -
                   
     | 
| 
       20 
     | 
    
         
            -
                     
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
       23 
     | 
    
         
            -
                    unless json
         
     | 
| 
       24 
     | 
    
         
            -
                      logger.error "Backtweets: #{response.code} #{response.message}"
         
     | 
| 
       25 
     | 
    
         
            -
                      raise "Last request didn't go as expected, trying again later"
         
     | 
| 
       26 
     | 
    
         
            -
                    end
         
     | 
| 
       27 
     | 
    
         
            -
                   
         
     | 
| 
       28 
     | 
    
         
            -
                    if tweets = json["tweets"]
         
     | 
| 
       29 
     | 
    
         
            -
                      last_tweet_id = source["last_tweet_id"]
         
     | 
| 
       30 
     | 
    
         
            -
                      if last_tweet_id
         
     | 
| 
      
 18 
     | 
    
         
            +
                def update(source, request, callback)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  session "backtweets.com" do |http|
         
     | 
| 
      
 20 
     | 
    
         
            +
                    http.get_json "/search.json?key=#{api_key}&q=#{Rack::Utils.escape source["url"]}" do |status, json|
         
     | 
| 
      
 21 
     | 
    
         
            +
                      if status == 200 && tweets = json["tweets"]
         
     | 
| 
      
 22 
     | 
    
         
            +
                        last_tweet_id = source["last_tweet_id"]
         
     | 
| 
       31 
23 
     | 
    
         
             
                        new_ones = tweets.take_while { |tweet| tweet["tweet_id"] != last_tweet_id }.reverse
         
     | 
| 
       32 
24 
     | 
    
         
             
                        new_ones.each do |tweet|
         
     | 
| 
       33 
     | 
    
         
            -
                           
     | 
| 
       34 
     | 
    
         
            -
                          url = "http://twitter.com/#{ 
     | 
| 
      
 25 
     | 
    
         
            +
                          screen_name, id = tweet["tweet_from_user"], tweet["tweet_id"]
         
     | 
| 
      
 26 
     | 
    
         
            +
                          url = "http://twitter.com/#{screen_name}/#{id}"
         
     | 
| 
       35 
27 
     | 
    
         
             
                          html = <<-HTML
         
     | 
| 
       36 
28 
     | 
    
         
             
            <a href="#{url}">tweeted</a>:
         
     | 
| 
       37 
29 
     | 
    
         
             
            <blockquote>#{tweet["tweet_text"]}</blockquote>
         
     | 
| 
       38 
30 
     | 
    
         
             
                          HTML
         
     | 
| 
       39 
     | 
    
         
            -
                          person = { : 
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
       41 
     | 
    
         
            -
             
     | 
| 
      
 31 
     | 
    
         
            +
                          person = { fullname: screen_name, identities: %W{twitter.com:#{screen_name}},
         
     | 
| 
      
 32 
     | 
    
         
            +
                                     photo_url: "http://img.tweetimag.es/i/#{screen_name}_n" }
         
     | 
| 
      
 33 
     | 
    
         
            +
                          callback.activity! uid: id, url: url, html: html, tags: %w{twitter mention},
         
     | 
| 
      
 34 
     | 
    
         
            +
                                             timestamp: Time.parse(tweet["tweet_created_at"]).utc, person: person
         
     | 
| 
      
 35 
     | 
    
         
            +
                          source["last_tweet_id"] = tweet["tweet_id"]
         
     | 
| 
       42 
36 
     | 
    
         
             
                        end
         
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
       44 
     | 
    
         
            -
                       
     | 
| 
       45 
     | 
    
         
            -
                         
     | 
| 
      
 37 
     | 
    
         
            +
                        callback.set! tweets: json["totalresults"].to_i
         
     | 
| 
      
 38 
     | 
    
         
            +
                      else
         
     | 
| 
      
 39 
     | 
    
         
            +
                        callback.error! "Last request didn't go as expected, trying again later"
         
     | 
| 
       46 
40 
     | 
    
         
             
                      end
         
     | 
| 
       47 
41 
     | 
    
         
             
                    end
         
     | 
| 
       48 
     | 
    
         
            -
             
     | 
| 
       49 
     | 
    
         
            -
                    block.call :set=>{ :tweets=>json["totalresults"].to_i }
         
     | 
| 
       50 
42 
     | 
    
         
             
                  end
         
     | 
| 
       51 
43 
     | 
    
         
             
                end
         
     | 
| 
       52 
44 
     | 
    
         | 
| 
       53 
45 
     | 
    
         
             
                def meta(source)
         
     | 
| 
       54 
     | 
    
         
            -
                  [ { : 
     | 
| 
      
 46 
     | 
    
         
            +
                  [ { text: "Search yourself", url: "http://backtweets.com/search?q=#{URI.escape source["url"]}" } ]
         
     | 
| 
       55 
47 
     | 
    
         
             
                end
         
     | 
| 
       56 
48 
     | 
    
         
             
              end
         
     | 
| 
       57 
49 
     | 
    
         
             
            end
         
     | 
| 
         @@ -6,7 +6,7 @@ module DashFu::Mario 
     | 
|
| 
       6 
6 
     | 
    
         
             
                def setup(source, params)
         
     | 
| 
       7 
7 
     | 
    
         
             
                  repo = params["repo"].strip
         
     | 
| 
       8 
8 
     | 
    
         
             
                  source["source.name"] = "Github: #{repo}"
         
     | 
| 
       9 
     | 
    
         
            -
                  source["metric.columns"] = [{ : 
     | 
| 
      
 9 
     | 
    
         
            +
                  source["metric.columns"] = [{ id: "commits", label: "Commits" }, { id: "watchers", label: "Watchers" }, { id: "forks", label: "Forks" }]
         
     | 
| 
       10 
10 
     | 
    
         
             
                  source["metric.totals"] = true
         
     | 
| 
       11 
11 
     | 
    
         
             
                  source["repo"] = repo
         
     | 
| 
       12 
12 
     | 
    
         
             
                  branch = params["branch"].strip
         
     | 
| 
         @@ -20,80 +20,73 @@ module DashFu::Mario 
     | 
|
| 
       20 
20 
     | 
    
         
             
                  raise "Need user name and repository name, e.g. assaf/vanity" unless source["repo"][/^[\w-]+\/[\w-]+$/]
         
     | 
| 
       21 
21 
     | 
    
         
             
                end
         
     | 
| 
       22 
22 
     | 
    
         | 
| 
       23 
     | 
    
         
            -
                def update(source, request,  
     | 
| 
       24 
     | 
    
         
            -
                   
     | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
     | 
    
         
            -
                       
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
     | 
    
         
            -
                       
     | 
| 
       38 
     | 
    
         
            -
             
     | 
| 
       39 
     | 
    
         
            -
                       
     | 
| 
       40 
     | 
    
         
            -
                      error = true
         
     | 
| 
       41 
     | 
    
         
            -
                    end
         
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
       43 
     | 
    
         
            -
                    case response = http_request(http, source, "/api/v2/json/commits/list/:repo/:branch")
         
     | 
| 
       44 
     | 
    
         
            -
                    when Net::HTTPOK
         
     | 
| 
       45 
     | 
    
         
            -
                      commits = JSON.parse(response.body)["commits"] rescue nil
         
     | 
| 
       46 
     | 
    
         
            -
                    when Net::HTTPNotFound, Net::HTTPBadRequest
         
     | 
| 
       47 
     | 
    
         
            -
                      raise "Could not find the branch #{source["branch"]}"
         
     | 
| 
      
 23 
     | 
    
         
            +
                def update(source, request, callback)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  session "github.com", 443 do |http|
         
     | 
| 
      
 25 
     | 
    
         
            +
                    auth = { username: "#{source["username"]}/token", password: source["api_token"] }
         
     | 
| 
      
 26 
     | 
    
         
            +
                    http.get_json "/api/v2/json/repos/show/#{source["repo"]}", auth do |status, json|
         
     | 
| 
      
 27 
     | 
    
         
            +
                      case status
         
     | 
| 
      
 28 
     | 
    
         
            +
                      when 200
         
     | 
| 
      
 29 
     | 
    
         
            +
                        if repository = json["repository"]
         
     | 
| 
      
 30 
     | 
    
         
            +
                          source.update repository.slice(*%w{description url homepage})
         
     | 
| 
      
 31 
     | 
    
         
            +
                          callback.set! forks: repository["forks"], watchers: repository["watchers"]
         
     | 
| 
      
 32 
     | 
    
         
            +
                        end
         
     | 
| 
      
 33 
     | 
    
         
            +
                      when 404, 400
         
     | 
| 
      
 34 
     | 
    
         
            +
                        callback.error! "Could not find the repository #{source["repo"]}"
         
     | 
| 
      
 35 
     | 
    
         
            +
                      when 401
         
     | 
| 
      
 36 
     | 
    
         
            +
                        callback.error! "You are not authorized to access this repository, or invalid username/password"
         
     | 
| 
      
 37 
     | 
    
         
            +
                      else
         
     | 
| 
      
 38 
     | 
    
         
            +
                        callback.error! "Last request didn't go as expected, trying again later"
         
     | 
| 
      
 39 
     | 
    
         
            +
                      end
         
     | 
| 
       48 
40 
     | 
    
         
             
                    end
         
     | 
| 
       49 
41 
     | 
    
         | 
| 
       50 
     | 
    
         
            -
                     
     | 
| 
       51 
     | 
    
         
            -
                       
     | 
| 
       52 
     | 
    
         
            -
                       
     | 
| 
       53 
     | 
    
         
            -
                         
     | 
| 
       54 
     | 
    
         
            -
             
     | 
| 
       55 
     | 
    
         
            -
                           
     | 
| 
       56 
     | 
    
         
            -
                           
     | 
| 
       57 
     | 
    
         
            -
                            all.last  
     | 
| 
       58 
     | 
    
         
            -
             
     | 
| 
       59 
     | 
    
         
            -
             
     | 
| 
      
 42 
     | 
    
         
            +
                    http.get_json "/api/v2/json/commits/list/#{source["repo"]}/#{source["branch"]}", auth do |status, json|
         
     | 
| 
      
 43 
     | 
    
         
            +
                      case status
         
     | 
| 
      
 44 
     | 
    
         
            +
                      when 200
         
     | 
| 
      
 45 
     | 
    
         
            +
                        if commits = json["commits"]
         
     | 
| 
      
 46 
     | 
    
         
            +
                          last_seen_id = source["last_commit"]["id"] if source["last_commit"]
         
     | 
| 
      
 47 
     | 
    
         
            +
                          new_ones = commits.take_while { |commit| commit["id"] != last_seen_id }
         
     | 
| 
      
 48 
     | 
    
         
            +
                          merged = new_ones.inject([]) do |all, commit|
         
     | 
| 
      
 49 
     | 
    
         
            +
                            last = all.last.last unless all.empty?
         
     | 
| 
      
 50 
     | 
    
         
            +
                            if last && last["committer"]["email"] == commit["committer"]["email"] &&
         
     | 
| 
      
 51 
     | 
    
         
            +
                              Time.parse(last["committed_date"]) - Time.parse(commit["committed_date"]) < 1.hour
         
     | 
| 
      
 52 
     | 
    
         
            +
                              all.last << commit
         
     | 
| 
      
 53 
     | 
    
         
            +
                            else
         
     | 
| 
      
 54 
     | 
    
         
            +
                              all << [commit]
         
     | 
| 
      
 55 
     | 
    
         
            +
                            end
         
     | 
| 
      
 56 
     | 
    
         
            +
                            all
         
     | 
| 
       60 
57 
     | 
    
         
             
                          end
         
     | 
| 
       61 
     | 
    
         
            -
                          all
         
     | 
| 
       62 
     | 
    
         
            -
                        end
         
     | 
| 
       63 
58 
     | 
    
         | 
| 
       64 
     | 
    
         
            -
             
     | 
| 
       65 
     | 
    
         
            -
             
     | 
| 
       66 
     | 
    
         
            -
             
     | 
| 
       67 
     | 
    
         
            -
             
     | 
| 
       68 
     | 
    
         
            -
             
     | 
| 
       69 
     | 
    
         
            -
             
     | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
     | 
    
         
            -
             
     | 
| 
      
 59 
     | 
    
         
            +
                          merged.reverse.each do |commits|
         
     | 
| 
      
 60 
     | 
    
         
            +
                            first = commits.first
         
     | 
| 
      
 61 
     | 
    
         
            +
                            committer = first["committer"]
         
     | 
| 
      
 62 
     | 
    
         
            +
                            person = { fullname: committer["name"], identities: %W{github.com:#{committer["login"]}}, email: committer["email"] }
         
     | 
| 
      
 63 
     | 
    
         
            +
                            messages = commits.map { |commit| %{<blockquote><a href="#{commit["url"]}">#{commit["id"][0,7]}</a> #{h commit["message"].strip.split(/[\n\r]/).first[0,50]}</blockquote>} }
         
     | 
| 
      
 64 
     | 
    
         
            +
                            html = %{pushed to #{h source["branch"]} at <a href="http://github.com/#{source["repo"]}">#{h source["repo"]}</a>:\n#{messages.join("\n")}}
         
     | 
| 
      
 65 
     | 
    
         
            +
                            callback.activity! uid: first["id"], html: html, url: first["url"], tags: %w{push},
         
     | 
| 
      
 66 
     | 
    
         
            +
                                               timestamp: Time.parse(first["committed_date"]).utc, person: person
         
     | 
| 
      
 67 
     | 
    
         
            +
                          end
         
     | 
| 
      
 68 
     | 
    
         
            +
                          callback.inc! commits: new_ones.count
         
     | 
| 
      
 69 
     | 
    
         
            +
                          if last_commit = commits.first
         
     | 
| 
      
 70 
     | 
    
         
            +
                            source.update "last_commit"=>last_commit.slice("id", "message", "url").
         
     | 
| 
      
 71 
     | 
    
         
            +
                              merge("timestamp"=>Time.parse(last_commit["committed_date"]).utc)
         
     | 
| 
      
 72 
     | 
    
         
            +
                          end
         
     | 
| 
       72 
73 
     | 
    
         
             
                        end
         
     | 
| 
       73 
     | 
    
         
            -
             
     | 
| 
      
 74 
     | 
    
         
            +
                      when 404, 400
         
     | 
| 
      
 75 
     | 
    
         
            +
                        callback.error! "Could not find the branch #{source["branch"]}"
         
     | 
| 
       74 
76 
     | 
    
         
             
                      else
         
     | 
| 
       75 
     | 
    
         
            -
                         
     | 
| 
      
 77 
     | 
    
         
            +
                        callback.error! "Github: #{response.code} #{response.message}"
         
     | 
| 
       76 
78 
     | 
    
         
             
                      end
         
     | 
| 
       77 
     | 
    
         
            -
                      if last_commit = commits.first
         
     | 
| 
       78 
     | 
    
         
            -
                        source.update "last_commit"=>last_commit.slice("id", "message", "url").
         
     | 
| 
       79 
     | 
    
         
            -
                          merge("timestamp"=>Time.parse(last_commit["committed_date"]).utc)
         
     | 
| 
       80 
     | 
    
         
            -
                      end
         
     | 
| 
       81 
     | 
    
         
            -
                    else
         
     | 
| 
       82 
     | 
    
         
            -
                      logger.error "Github: #{response.code} #{response.message}"
         
     | 
| 
       83 
     | 
    
         
            -
                      error = true
         
     | 
| 
       84 
79 
     | 
    
         
             
                    end
         
     | 
| 
       85 
     | 
    
         
            -
             
     | 
| 
       86 
     | 
    
         
            -
                    raise "Last request didn't go as expected, trying again later" if error
         
     | 
| 
       87 
80 
     | 
    
         
             
                  end
         
     | 
| 
       88 
81 
     | 
    
         
             
                end
         
     | 
| 
       89 
82 
     | 
    
         | 
| 
       90 
83 
     | 
    
         
             
                def meta(source)
         
     | 
| 
       91 
     | 
    
         
            -
                  meta = [ { : 
     | 
| 
       92 
     | 
    
         
            -
                           { : 
     | 
| 
       93 
     | 
    
         
            -
                           { : 
     | 
| 
       94 
     | 
    
         
            -
                           { : 
     | 
| 
      
 84 
     | 
    
         
            +
                  meta = [ { title: "Repository", text: source["repo"], url: source["url"] },
         
     | 
| 
      
 85 
     | 
    
         
            +
                           { title: "Branch", text: source["branch"] },
         
     | 
| 
      
 86 
     | 
    
         
            +
                           { text: source["description"] },
         
     | 
| 
      
 87 
     | 
    
         
            +
                           { title: "Home page", url: source["homepage"] } ]
         
     | 
| 
       95 
88 
     | 
    
         
             
                  if last_commit = source["last_commit"]
         
     | 
| 
       96 
     | 
    
         
            -
                    meta << { : 
     | 
| 
      
 89 
     | 
    
         
            +
                    meta << { title: "Commit", text: last_commit["message"], url: last_commit["url"] }
         
     | 
| 
       97 
90 
     | 
    
         
             
                  end
         
     | 
| 
       98 
91 
     | 
    
         
             
                  meta
         
     | 
| 
       99 
92 
     | 
    
         
             
                end
         
     | 
| 
         @@ -6,7 +6,7 @@ module DashFu::Mario 
     | 
|
| 
       6 
6 
     | 
    
         
             
                def setup(source, params)
         
     | 
| 
       7 
7 
     | 
    
         
             
                  repo = params["repo"].to_s.strip
         
     | 
| 
       8 
8 
     | 
    
         
             
                  source["source.name"] = "Github Issues for #{repo}"
         
     | 
| 
       9 
     | 
    
         
            -
                  source["metric.columns"] = [{ : 
     | 
| 
      
 9 
     | 
    
         
            +
                  source["metric.columns"] = [{ id: "open", label: "Open issues" }, { id: "closed", label: "Closed issues" }]
         
     | 
| 
       10 
10 
     | 
    
         
             
                  source["metric.totals"] = true
         
     | 
| 
       11 
11 
     | 
    
         
             
                  source["repo"] = repo
         
     | 
| 
       12 
12 
     | 
    
         
             
                  source["username"] = params["username"]
         
     | 
| 
         @@ -18,64 +18,63 @@ module DashFu::Mario 
     | 
|
| 
       18 
18 
     | 
    
         
             
                  raise "Need user name and repository name, e.g. assaf/vanity" unless source["repo"][/^[\w-]+\/[\w-]+$/]
         
     | 
| 
       19 
19 
     | 
    
         
             
                end
         
     | 
| 
       20 
20 
     | 
    
         | 
| 
       21 
     | 
    
         
            -
                def update(source, request,  
     | 
| 
       22 
     | 
    
         
            -
                   
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
                    if open
         
     | 
| 
       35 
     | 
    
         
            -
                      update[:open] = open.count
         
     | 
| 
       36 
     | 
    
         
            -
                      if open_ids = source["open-ids"]
         
     | 
| 
       37 
     | 
    
         
            -
                        open.reject { |issue| open_ids.include?(issue["number"]) }.reverse.each do |issue|
         
     | 
| 
       38 
     | 
    
         
            -
                          sha = Digest::SHA1.hexdigest([source["repo"], "open", issue["number"], issue["updated_at"]].join(":"))
         
     | 
| 
       39 
     | 
    
         
            -
                          url = "http://github.com/#{source["repo"]}/issues#issue/#{issue["number"]}"
         
     | 
| 
       40 
     | 
    
         
            -
                          html = <<-HTML
         
     | 
| 
      
 21 
     | 
    
         
            +
                def update(source, request, callback)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  session "github.com", 443 do |http|
         
     | 
| 
      
 23 
     | 
    
         
            +
                    auth = { username: "#{source["username"]}/token", password: source["api_token"] }
         
     | 
| 
      
 24 
     | 
    
         
            +
                    http.get_json "/api/v2/json/issues/list/#{source["repo"]}/open" do |status, json|
         
     | 
| 
      
 25 
     | 
    
         
            +
                      case status
         
     | 
| 
      
 26 
     | 
    
         
            +
                      when 200
         
     | 
| 
      
 27 
     | 
    
         
            +
                        if open = json["issues"]
         
     | 
| 
      
 28 
     | 
    
         
            +
                          callback.set! open: open.count
         
     | 
| 
      
 29 
     | 
    
         
            +
                          open_ids = Set.new(source["open_ids"])
         
     | 
| 
      
 30 
     | 
    
         
            +
                          open.reject { |issue| open_ids.include?(issue["number"]) }.reverse.each do |issue|
         
     | 
| 
      
 31 
     | 
    
         
            +
                            sha = Digest::SHA1.hexdigest([source["repo"], "open", issue["number"], issue["updated_at"]].join(":"))
         
     | 
| 
      
 32 
     | 
    
         
            +
                            url = "http://github.com/#{source["repo"]}/issues#issue/#{issue["number"]}"
         
     | 
| 
      
 33 
     | 
    
         
            +
                            html = <<-HTML
         
     | 
| 
       41 
34 
     | 
    
         
             
            opened <a href="#{url}">issue #{issue["number"]}</a> on #{source["repo"]}:
         
     | 
| 
       42 
35 
     | 
    
         
             
            <blockquote>#{h issue["title"]}</blockquote>
         
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
       44 
     | 
    
         
            -
             
     | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
      
 36 
     | 
    
         
            +
                            HTML
         
     | 
| 
      
 37 
     | 
    
         
            +
                            callback.activity! uid: sha, url: url, html: html, tags: %w{issue opened},
         
     | 
| 
      
 38 
     | 
    
         
            +
                                               timestamp: Time.parse(issue["created_at"]).utc
         
     | 
| 
      
 39 
     | 
    
         
            +
                            open_ids << issue["number"]
         
     | 
| 
      
 40 
     | 
    
         
            +
                          end
         
     | 
| 
      
 41 
     | 
    
         
            +
                          source["open_ids"] = open_ids.to_a
         
     | 
| 
       46 
42 
     | 
    
         
             
                        end
         
     | 
| 
      
 43 
     | 
    
         
            +
                      when 404, 400
         
     | 
| 
      
 44 
     | 
    
         
            +
                        callback.error! "Could not find the repository #{source["repo"]}"
         
     | 
| 
      
 45 
     | 
    
         
            +
                      when 401
         
     | 
| 
      
 46 
     | 
    
         
            +
                        callback.error! "You are not authorized to access this repository, or invalid username/password"
         
     | 
| 
      
 47 
     | 
    
         
            +
                      else
         
     | 
| 
      
 48 
     | 
    
         
            +
                        callback.error! "Last request didn't go as expected, trying again later"
         
     | 
| 
       47 
49 
     | 
    
         
             
                      end
         
     | 
| 
       48 
     | 
    
         
            -
                      source["open-ids"] = open.map { |issue| issue["number"] }
         
     | 
| 
       49 
50 
     | 
    
         
             
                    end
         
     | 
| 
       50 
51 
     | 
    
         | 
| 
       51 
     | 
    
         
            -
                     
     | 
| 
       52 
     | 
    
         
            -
             
     | 
| 
       53 
     | 
    
         
            -
                       
     | 
| 
       54 
     | 
    
         
            -
             
     | 
| 
       55 
     | 
    
         
            -
             
     | 
| 
       56 
     | 
    
         
            -
             
     | 
| 
       57 
     | 
    
         
            -
             
     | 
| 
       58 
     | 
    
         
            -
             
     | 
| 
       59 
     | 
    
         
            -
             
     | 
| 
       60 
     | 
    
         
            -
             
     | 
| 
       61 
     | 
    
         
            -
                          html = <<-HTML
         
     | 
| 
      
 52 
     | 
    
         
            +
                    http.get_json "/api/v2/json/issues/list/#{source["repo"]}/closed" do |status, json|
         
     | 
| 
      
 53 
     | 
    
         
            +
                      case status
         
     | 
| 
      
 54 
     | 
    
         
            +
                      when 200
         
     | 
| 
      
 55 
     | 
    
         
            +
                        if closed = json["issues"]
         
     | 
| 
      
 56 
     | 
    
         
            +
                          callback.set! closed: closed.count
         
     | 
| 
      
 57 
     | 
    
         
            +
                          closed_ids = Set.new(source["closed_ids"])
         
     | 
| 
      
 58 
     | 
    
         
            +
                          closed.reject { |issue| closed_ids.include?(issue["number"]) }.reverse.each do |issue|
         
     | 
| 
      
 59 
     | 
    
         
            +
                            sha = Digest::SHA1.hexdigest([source["repo"], "closed", issue["number"], issue["updated_at"]].join(":"))
         
     | 
| 
      
 60 
     | 
    
         
            +
                            url = "http://github.com/#{source["repo"]}/issues#issue/#{issue["number"]}"
         
     | 
| 
      
 61 
     | 
    
         
            +
                            html = <<-HTML
         
     | 
| 
       62 
62 
     | 
    
         
             
            closed <a href="#{url}">issue #{issue["number"]}</a> on #{source["repo"]}:
         
     | 
| 
       63 
63 
     | 
    
         
             
            <blockquote>#{h issue["title"]}</blockquote>
         
     | 
| 
       64 
     | 
    
         
            -
             
     | 
| 
       65 
     | 
    
         
            -
             
     | 
| 
       66 
     | 
    
         
            -
             
     | 
| 
      
 64 
     | 
    
         
            +
                            HTML
         
     | 
| 
      
 65 
     | 
    
         
            +
                            callback.activity! uid: sha, url: url, html: html, tags: %w{issue closed},
         
     | 
| 
      
 66 
     | 
    
         
            +
                                               timestamp: Time.parse(issue["closed_at"]).utc
         
     | 
| 
      
 67 
     | 
    
         
            +
                            closed_ids << issue["number"]
         
     | 
| 
      
 68 
     | 
    
         
            +
                          end
         
     | 
| 
      
 69 
     | 
    
         
            +
                          source["closed_ids"] = closed_ids.to_a
         
     | 
| 
       67 
70 
     | 
    
         
             
                        end
         
     | 
| 
       68 
71 
     | 
    
         
             
                      end
         
     | 
| 
       69 
     | 
    
         
            -
                      source["closed-ids"] = closed.map { |issue| issue["number"] }
         
     | 
| 
       70 
72 
     | 
    
         
             
                    end
         
     | 
| 
       71 
     | 
    
         
            -
             
     | 
| 
       72 
     | 
    
         
            -
                    raise "Last request didn't go as expected, trying again later" if update.empty?
         
     | 
| 
       73 
     | 
    
         
            -
                    block.call :set=>update
         
     | 
| 
       74 
73 
     | 
    
         
             
                  end
         
     | 
| 
       75 
74 
     | 
    
         
             
                end
         
     | 
| 
       76 
75 
     | 
    
         | 
| 
       77 
76 
     | 
    
         
             
                def meta(source)
         
     | 
| 
       78 
     | 
    
         
            -
                  [ { : 
     | 
| 
      
 77 
     | 
    
         
            +
                  [ { title: "On Github", url: "http://github.com/#{source["repo"]}/issues" } ]
         
     | 
| 
       79 
78 
     | 
    
         
             
                end
         
     | 
| 
       80 
79 
     | 
    
         | 
| 
       81 
80 
     | 
    
         
             
              protected
         
     |