diff --git a/Gemfile b/Gemfile index 93991be..f559d9c 100644 --- a/Gemfile +++ b/Gemfile @@ -24,5 +24,6 @@ gem "dotenv" group :development do gem "foreman" gem "pry" - gem "rack-test" + # 0.6.2 on rubygems is too old, lacks things like 'env' + gem "rack-test", :github => "brynary/rack-test", :ref => "8cdb86e1d7" end diff --git a/Gemfile.lock b/Gemfile.lock index 5b885d8..cf2e797 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,11 @@ +GIT + remote: git://github.com/brynary/rack-test.git + revision: 8cdb86e1d710194ce45f439915707db7fb2b27e5 + ref: 8cdb86e1d7 + specs: + rack-test (0.6.2) + rack (>= 1.0) + GEM remote: http://rubygems.org/ specs: @@ -54,8 +62,6 @@ GEM rack-protection (1.5.2) rack rack-ssl-enforcer (0.2.6) - rack-test (0.6.2) - rack (>= 1.0) rake (10.1.1) redis (3.0.6) rest-client (1.6.7) @@ -117,7 +123,7 @@ DEPENDENCIES pry rack-canonical-host rack-ssl-enforcer - rack-test + rack-test! rake rest-client rspec diff --git a/lib/descartes/public/js/list-metrics.js b/lib/descartes/public/js/list-metrics.js index 994e2df..d91489d 100644 --- a/lib/descartes/public/js/list-metrics.js +++ b/lib/descartes/public/js/list-metrics.js @@ -37,22 +37,30 @@ var addMetricsToolbar = function(metric) { html('12'); }; -// Grab configuration blob and construct our graph urls -var renderGraphs = function() { - // Load index.json at first load and cache as myLoadedMetrics - // Use cached myLoadedMetrics for infinite scrolling - if (myLoadedMetrics.length > 0) { - // Use cached myLoadedMetrics - renderSparklines(); - } else { - // Load index.json and cache in myLoadedMetrics +// Load a page of metrics from backend +var loadMetricsPage = function(page) { return $.ajax({ accepts: {'json': 'application/json'}, cache: false, dataType: 'json', error: function(xhr, textStatus, errorThrown) { console.log(errorThrown); }, - url: '/metrics/' - }).done(function(d) { + // Load first page by default + url: '/metrics/', + data: {limit: myMetricsPerPage, page: page} + }); +} + + +// Render current page of graphs, querying 1st page if none stored +var renderGraphs = function() { + // loaded metrics non-empty? just render 'em + if (myLoadedMetrics.length > 0) { + // Use cached myLoadedMetrics + renderSparklines(); + // empty? grab 1st page. + } else { + $('div.loading').removeClass('hidden'); + return loadMetricsPage(1).done(function(d) { if (d.length === 0) { console.log('No metrics found'); } else { @@ -67,15 +75,18 @@ var renderSparklines = function() { // Show our loading div $('div.loading').removeClass('hidden'); - if (myMatchedMetrics.length === 0) { - myMatchedMetrics = myLoadedMetrics; + // Use loaded-from-beginning metric cache by default; + // but if search results seem to exist, use those instead. + var myLocalMetrics = myLoadedMetrics; + if (myMatchedMetrics.length != 0) { + myLocalMetrics = myMatchedMetrics; } for (var target = ((myPageIndex - 1) * myMetricsPerPage); target < (myPageIndex * myMetricsPerPage); target += 1) { // Only run if we have an actual target, ignore end of page undefs - if (myMatchedMetrics[target] !== undefined) { + if (myLocalMetrics[target] !== undefined) { // unique identifier so we can track each metric to its DOM element - hash = CryptoJS.SHA256(myMatchedMetrics[target]).toString(CryptoJS.enc.Hex); + hash = CryptoJS.SHA256(myLocalMetrics[target]).toString(CryptoJS.enc.Hex); $('div.metrics').append('
'); $.ajax({ accepts: {'json': 'application/json'}, @@ -91,8 +102,8 @@ var renderSparklines = function() { dataType: 'json', error: function(xhr, textStatus, errorThrown) { console.log(errorThrown); }, hash: hash, - target: myMatchedMetrics[target], - url: graphiteUrl + '/render?target=' + myMatchedMetrics[target] + '&from=-' + myInterval + '&format=json' + target: myLocalMetrics[target], + url: graphiteUrl + '/render?target=' + myLocalMetrics[target] + '&from=-' + myInterval + '&format=json' }).done(function(output) { if (output.length === 1) { var data = []; @@ -134,7 +145,7 @@ var renderSparklines = function() { } } // Disable infinite scrolling - if (myPageIndex * myMetricsPerPage > myMatchedMetrics.length) { + if (myPageIndex * myMetricsPerPage > myLocalMetrics.length) { myPageIndex = 0; } }; @@ -242,8 +253,16 @@ var scrollNextPage = function() { if (!this.lastCall || (delta > threshold)) { this.lastCall = now; myPageIndex += 1; - console.log("loading more metrics, myPageIndex is now " + myPageIndex); - renderGraphs(); + // Actually load more metrics + loadMetricsPage(myPageIndex).done(function(d) { + if (d.length != 0) { + myLoadedMetrics = myLoadedMetrics.concat(d); + } + // Ensure the new metrics get rendered (basically just passes + // through to renderSparklines). Do this inside the callback or + // things don't line up right. + renderGraphs(); + }); } else { console.log("throttling"); } diff --git a/lib/descartes/routes/metrics.rb b/lib/descartes/routes/metrics.rb index 81f01da..630fe8d 100644 --- a/lib/descartes/routes/metrics.rb +++ b/lib/descartes/routes/metrics.rb @@ -3,9 +3,22 @@ class Web < Sinatra::Base get '/metrics/?' do if request.xhr? + # No params -> just return everything. + metrics = if %w(page limit).map {|x| params.include?(x)}.none? + Metric.all + # Params -> paginate, depending. + else + page = (params['page'] || 1).to_i + size = (params['limit'] || 50).to_i + start = (page - 1) * size + end_ = start + size + Metric.all[start...end_] + end + + # Response content_type "application/json" status 200 - Metric.all.to_json + metrics.to_json else haml :'metrics/index', :locals => { :title => "Descartes - Metrics List", :cache_age => MetricCacheStatus.first.updated_at.to_s } end @@ -22,4 +35,4 @@ class Web < Sinatra::Base end end -end \ No newline at end of file +end diff --git a/spec/web_spec.rb b/spec/web_spec.rb index 6b61ae6..b7ea265 100644 --- a/spec/web_spec.rb +++ b/spec/web_spec.rb @@ -1,7 +1,28 @@ +require 'rspec' require 'rack/test' require 'spec_helper' require './lib/descartes/web' +require './lib/descartes/models/metrics' + + +# Meh? +class Metric + def self.paths=(paths) + @@paths = paths + end +end + +# Don't see a way to do this natively in Rack. 'response.json' idea stolen +# from python-requests lib. +module Rack + class Response + def json + JSON.parse(body) + end + end +end + describe Descartes::Web do include Rack::Test::Methods @@ -10,5 +31,45 @@ def app Descartes::Web end - it 'should have some tests' + it 'should have more tests' + + describe '/metrics/' do + context 'json' do + before :each do + # Be an API request. + env 'HTTP_X_DESCARTES_API_TOKEN', ENV['API_TOKEN'] + header 'Accept', 'application/json' + # Fake metric paths + Metric.paths = 1.upto(100).to_a + end + + def assert_array(size, start=nil, end_=nil) + expect(last_response.ok?).to be_true # Fail fast + result = last_response.json + expect(result.size).to eq(size) + expect(result.first).to(eq(start)) unless start.nil? + expect(result.last).to(eq(end_)) unless end_.nil? + end + + it 'without page/limit params, loads entire cache' do + get '/metrics/', :_ => 12345 # ape real webapp behavior + assert_array 100 + end + + it 'loads specific pages when requested via :page' do + get '/metrics/', :page => 1 + assert_array 50, 1, 50 + end + + it 'changes page size optionally via :limit' do + get '/metrics/', :page => 2, :limit => 25 + assert_array 25, 26, 50 + end + + it 'assumes page 1 when :limit given & no :page' do + get '/metrics/', :limit => 25 + assert_array 25, 1, 25 + end + end + end end