Skip to content
This repository has been archived by the owner on Jan 19, 2022. It is now read-only.

Paginated metrics #160

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
12 changes: 9 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -117,7 +123,7 @@ DEPENDENCIES
pry
rack-canonical-host
rack-ssl-enforcer
rack-test
rack-test!
rake
rest-client
rspec
Expand Down
59 changes: 39 additions & 20 deletions lib/descartes/public/js/list-metrics.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,30 @@ var addMetricsToolbar = function(metric) {
html('<a class="axis left selected" title="Left axis" href="#">1</a><a class="axis right" title="Right axis" href="#">2</a>');
};

// 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 {
Expand All @@ -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('<div class="default" id="' + hash + '"></div>');
$.ajax({
accepts: {'json': 'application/json'},
Expand All @@ -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 = [];
Expand Down Expand Up @@ -134,7 +145,7 @@ var renderSparklines = function() {
}
}
// Disable infinite scrolling
if (myPageIndex * myMetricsPerPage > myMatchedMetrics.length) {
if (myPageIndex * myMetricsPerPage > myLocalMetrics.length) {
myPageIndex = 0;
}
};
Expand Down Expand Up @@ -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");
}
Expand Down
17 changes: 15 additions & 2 deletions lib/descartes/routes/metrics.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -22,4 +35,4 @@ class Web < Sinatra::Base
end

end
end
end
63 changes: 62 additions & 1 deletion spec/web_spec.rb
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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