From f848376e90bbfc9c27f333a21d080dc6b7e4ee73 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Sun, 15 Apr 2012 21:05:59 -0700 Subject: [PATCH] Replace Cube front-end with node-static. The Cube front-end is now just a static file server (in addition to the /event and /metric endpoints that power the collector and evaluator). This removes the visualization component from Cube, so that it can focus on data collection and metric computation. To make visualizations, you now simply write a bit of HTML and JavaScript to fetch and display metrics. This commit will followup with some examples that demonstrate the technique. Also, add a 1-minute tier, and remove the week and year tiers. The highest tier is now the 1-day tier. We could still use the old tiers, but there's a little bit of overhead to invalidate every tier for every event; perhaps in the future, we'll want to configure tiers on a per-event basis. --- Makefile | 4 +- bin/evaluator.js | 1 - lib/cube/client/board.css | 12 - lib/cube/client/board.js | 225 ---------------- lib/cube/client/body.css | 87 ------- lib/cube/client/cube.js | 3 - lib/cube/client/end.js | 1 - lib/cube/client/header.js | 58 ----- lib/cube/client/palette.css | 12 - lib/cube/client/palette.js | 63 ----- lib/cube/client/piece-area.js | 255 ------------------ lib/cube/client/piece-sum.js | 137 ---------- lib/cube/client/piece-text.js | 65 ----- lib/cube/client/piece.css | 151 ----------- lib/cube/client/piece.js | 270 -------------------- lib/cube/client/semicolon.js | 1 - lib/cube/client/squares.js | 45 ---- lib/cube/client/start.js | 1 - lib/cube/client/visualizer.html | 24 -- lib/cube/{server => }/collectd.js | 0 lib/cube/collector.js | 44 ++++ lib/cube/{server => }/emitter.js | 0 lib/cube/endpoint.js | 8 + lib/cube/{server => }/evaluator.js | 16 +- lib/cube/{server => }/event-expression.js | 0 lib/cube/{server => }/event-expression.peg | 0 lib/cube/{server => }/event.js | 0 lib/cube/index.js | 12 +- lib/cube/{server => }/metric-expression.js | 0 lib/cube/{server => }/metric-expression.peg | 0 lib/cube/{server => }/metric.js | 0 lib/cube/{server => }/reduces.js | 0 lib/cube/{server => }/server.js | 15 +- lib/cube/server/collector.js | 41 --- lib/cube/server/endpoint.js | 86 ------- lib/cube/server/visualizer.js | 191 -------------- lib/cube/{server => }/tiers.js | 30 +-- lib/cube/{server => }/types.js | 0 package.json | 4 +- test/collector-test.js | 8 +- test/endpoint-test.js | 65 ----- test/event-expression-test.js | 2 +- test/metric-expression-test.js | 2 +- test/metric-test.js | 45 ++-- test/reduces-test.js | 2 +- test/tiers-test.js | 195 +++++--------- test/types-test.js | 2 +- 47 files changed, 183 insertions(+), 2000 deletions(-) delete mode 100644 lib/cube/client/board.css delete mode 100644 lib/cube/client/board.js delete mode 100644 lib/cube/client/body.css delete mode 100644 lib/cube/client/cube.js delete mode 100644 lib/cube/client/end.js delete mode 100644 lib/cube/client/header.js delete mode 100644 lib/cube/client/palette.css delete mode 100644 lib/cube/client/palette.js delete mode 100644 lib/cube/client/piece-area.js delete mode 100644 lib/cube/client/piece-sum.js delete mode 100644 lib/cube/client/piece-text.js delete mode 100644 lib/cube/client/piece.css delete mode 100644 lib/cube/client/piece.js delete mode 100644 lib/cube/client/semicolon.js delete mode 100644 lib/cube/client/squares.js delete mode 100644 lib/cube/client/start.js delete mode 100644 lib/cube/client/visualizer.html rename lib/cube/{server => }/collectd.js (100%) create mode 100644 lib/cube/collector.js rename lib/cube/{server => }/emitter.js (100%) create mode 100644 lib/cube/endpoint.js rename lib/cube/{server => }/evaluator.js (89%) rename lib/cube/{server => }/event-expression.js (100%) rename lib/cube/{server => }/event-expression.peg (100%) rename lib/cube/{server => }/event.js (100%) rename lib/cube/{server => }/metric-expression.js (100%) rename lib/cube/{server => }/metric-expression.peg (100%) rename lib/cube/{server => }/metric.js (100%) rename lib/cube/{server => }/reduces.js (100%) rename lib/cube/{server => }/server.js (90%) delete mode 100644 lib/cube/server/collector.js delete mode 100644 lib/cube/server/endpoint.js delete mode 100644 lib/cube/server/visualizer.js rename lib/cube/{server => }/tiers.js (61%) rename lib/cube/{server => }/types.js (100%) delete mode 100644 test/endpoint-test.js diff --git a/Makefile b/Makefile index d81a2ea5..759e5661 100644 --- a/Makefile +++ b/Makefile @@ -7,8 +7,8 @@ PEG_COMPILER = ./node_modules/pegjs/bin/pegjs $(PEG_COMPILER) < $< > $@ all: \ - lib/cube/server/event-expression.js \ - lib/cube/server/metric-expression.js + lib/cube/event-expression.js \ + lib/cube/metric-expression.js test: all @$(JS_TESTER) diff --git a/bin/evaluator.js b/bin/evaluator.js index 45ba7001..b5da8894 100644 --- a/bin/evaluator.js +++ b/bin/evaluator.js @@ -4,7 +4,6 @@ var options = require("./evaluator-config"), server.register = function(db, endpoints) { cube.evaluator.register(db, endpoints); - cube.visualizer.register(db, endpoints); }; server.start(); diff --git a/lib/cube/client/board.css b/lib/cube/client/board.css deleted file mode 100644 index 54164b11..00000000 --- a/lib/cube/client/board.css +++ /dev/null @@ -1,12 +0,0 @@ -.squares rect { - fill: none; - stroke: #ddd; -} - -.squares rect.black { - fill: #fafafa; -} - -.squares rect.shadow { - fill: #e7e7e7; -} diff --git a/lib/cube/client/board.js b/lib/cube/client/board.js deleted file mode 100644 index 0fe94a80..00000000 --- a/lib/cube/client/board.js +++ /dev/null @@ -1,225 +0,0 @@ -cube.board = function(url, id) { - var board = {id: cube_board_formatId(id)}, - socket, - interval, - pieceId = 0, - palette, - squares, - pieces = [], - size = [32, 18], // in number of squares - squareSize = 40, // in pixels - squareRadius = 4, // in pixels - padding = 9.5; // half-pixel for crisp strokes - - var event = d3.dispatch( - "size", - "squareSize", - "squareRadius", - "padding", - "view" - ); - - var svg = document.createElementNS(d3.ns.prefix.svg, "svg"); - - d3.select(svg) - .attr("class", "board"); - - event.on("size.board", resize); - event.on("squareSize.board", resize); - event.on("squareRadius.board", resize); - event.on("padding.board", resize); - - function message(message) { - var e = JSON.parse(message.data); - switch (e.type) { - case "view": { - event.view.call(board, e); - break; - } - case "add": { - var piece = board.add(cube.piece.type[e.piece.type]) - .fromJSON(e.piece) - .on("move.board", move); - - d3.select(piece.node()) - .style("opacity", 1e-6) - .transition() - .duration(500) - .style("opacity", 1); - - pieceId = Math.max(pieceId, piece.id = e.piece.id); - break; - } - case "edit": { - pieces.some(function(piece) { - if (piece.id == e.piece.id) { - piece - .on("move.board", null) - .transition(d3.transition().duration(500)) - .fromJSON(e.piece); - - // Renable events after transition starts. - d3.timer(function() { piece.on("move.board", move); }, 250); - return true; - } - }); - break; - } - case "move": { - pieces.some(function(piece) { - if (piece.id == e.piece.id) { - piece - .on("move.board", null) - .transition(d3.transition().duration(500)) - .size(e.piece.size) - .position(e.piece.position); - - // Bring to front. - svg.parentNode.appendChild(piece.node()); - - // Renable events after transition starts. - d3.timer(function() { piece.on("move.board", move); }, 250); - return true; - } - }); - break; - } - case "remove": { - pieces.some(function(piece) { - if (piece.id == e.piece.id) { - board.remove(piece, true); - return true; - } - }); - break; - } - } - } - - function reopen() { - if (socket) { - pieces.slice().forEach(function(piece) { board.remove(piece, true); }); - socket.close(); - } - socket = new WebSocket(url); - socket.onopen = load; - socket.onmessage = message; - if (!interval) interval = setInterval(ping, 5000); - } - - function load() { - if (id && socket && socket.readyState == 1) { - socket.send(JSON.stringify({type: "load", id: id})); - } - } - - function ping() { - if (socket.readyState == 1) { - socket.send(JSON.stringify({type: "ping", id: id})); - } else if (socket.readyState > 1) { - reopen(); - } - } - - // A one-time listener to send an add event on mouseup. - function add() { - socket.send(JSON.stringify({type: "add", id: id, piece: this})); - this.on("move.board", move); - } - - function move() { - socket.send(JSON.stringify({type: "move", id: id, piece: this})); - } - - function edit() { - socket.send(JSON.stringify({type: "edit", id: id, piece: this})); - } - - function resize() { - d3.select(svg) - .attr("width", size[0] * squareSize + 2 * padding) - .attr("height", (size[1] + 2) * squareSize + 2 * padding); - - d3.select(palette.node()) - .attr("transform", "translate(" + padding + "," + padding + ")"); - - d3.select(squares.node()) - .attr("transform", "translate(" + padding + "," + (1.5 * squareSize + padding) + ")"); - } - - board.node = function() { - return svg; - }; - - board.on = function(type, listener) { - event.on(type, listener); - return board; - }; - - board.size = function(x) { - if (!arguments.length) return size; - event.size.call(board, size = x); - return board; - }; - - board.squareSize = function(x) { - if (!arguments.length) return squareSize; - event.squareSize.call(board, squareSize = x); - return board; - }; - - board.squareRadius = function(x) { - if (!arguments.length) return squareRadius; - event.squareRadius.call(board, squareRadius = x); - return board; - }; - - board.padding = function(x) { - if (!arguments.length) return padding; - event.padding.call(board, padding = x); - return board; - }; - - board.add = function(type) { - var piece = type(board); - piece.id = ++pieceId; - piece.on("move.board", add).on("edit.board", edit); - svg.parentNode.appendChild(piece.node()); - pieces.push(piece); - return piece; - }; - - board.remove = function(piece, silent) { - piece.on("move.board", null).on("edit.board", null); - if (silent) { - d3.select(piece.node()) - .style("opacity", 1) - .transition() - .duration(500) - .style("opacity", 1e-6) - .remove(); - } else { - socket.send(JSON.stringify({type: "remove", id: id, piece: {id: piece.id}})); - svg.parentNode.removeChild(piece.node()); - } - pieces.splice(pieces.indexOf(piece), 1); - return piece; - }; - - board.toJSON = function() { - return {id: id, size: size, pieces: pieces}; - }; - - svg.appendChild((palette = cube.palette(board)).node()); - svg.appendChild((squares = cube.squares(board)).node()); - resize(); - reopen(); - - return board; -}; - -function cube_board_formatId(id) { - id = id.toString(36); - if (id.length < 6) id = new Array(7 - id.length).join("0") + id; - return id; -} diff --git a/lib/cube/client/body.css b/lib/cube/client/body.css deleted file mode 100644 index 2f50fc51..00000000 --- a/lib/cube/client/body.css +++ /dev/null @@ -1,87 +0,0 @@ -body { - overflow-x: hidden; - overflow-y: scroll; - margin-top: 50px; -} - -body, button { - font: 14px "Helvetica Neue"; -} - -#header { - display: block; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 40px; - background: #222; - background: -webkit-gradient(linear,left top,left bottom,from(#333),to(#111)); - color: #fff; - -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.5); - z-index: 1; -} - -.view #header { - top: -60px; -} - -.header { - height: 40px; - line-height: 40px; - width: 1281px; -} - -.header, #board { - display: block; - margin: auto; - position: relative; -} - -#board { - width: 1299px; -} - -.left { - float: left; -} - -.right { - float: right; -} - -.right button { - margin: 0 0 0 4px; -} - -svg { - display: block; -} - -button { - background: #333; - background: -webkit-gradient(linear,left top,left bottom,from(#444),to(#222)); - color: #fff; - cursor: pointer; - height: 30px; - width: 113px; - margin: 0 4px 0 0; - padding: 0; - border: solid 1px #000; - border-radius: 4px; -} - -.view { - margin-top: -60px; - overflow: hidden; -} - -.view .squares { - display: none; -} - -@media screen and (min-width: 1920px) { - .view { - -webkit-transform: scale(1.4,1.4)translate(0px,110px); - } -} diff --git a/lib/cube/client/cube.js b/lib/cube/client/cube.js deleted file mode 100644 index 946bd25a..00000000 --- a/lib/cube/client/cube.js +++ /dev/null @@ -1,3 +0,0 @@ -cube = {version: "0.0.1"}; - -var cube_time = d3.time.format.iso; diff --git a/lib/cube/client/end.js b/lib/cube/client/end.js deleted file mode 100644 index 0319a0fe..00000000 --- a/lib/cube/client/end.js +++ /dev/null @@ -1 +0,0 @@ -})(); diff --git a/lib/cube/client/header.js b/lib/cube/client/header.js deleted file mode 100644 index 50415d62..00000000 --- a/lib/cube/client/header.js +++ /dev/null @@ -1,58 +0,0 @@ -cube.header = function(board) { - var header = {}; - - var div = document.createElement("div"); - - var selection = d3.select(div) - .attr("class", "header"); - - var left = selection.append("div") - .attr("class", "left"); - - left.append("a") - .attr("href", "/" + board.id) - .append("button") - .text("View"); - - left.append("a") - .attr("href", "/" + board.id + "/edit") - .append("button") - .text("Edit"); - - var viewers = selection.append("div") - .attr("class", "right"); - - board.on("view", function(e) { - viewers.text(e.count > 1 ? e.count - 1 + " other" + (e.count > 2 ? "s" : "") + " viewing" : null); - }); - - header.node = function() { - return div; - }; - - if (mode == "view") { - var shown = false; - - d3.select(window) - .on("mouseout", mouseout) - .on("mousemove", mousemove); - - function show(show) { - if (show != shown) { - d3.select(div.parentNode).transition() - .style("top", ((shown = show) ? 0 : -60) + "px"); - } - } - - function mouseout() { - if (d3.event.relatedTarget == null) show(false); - } - - function mousemove() { - if (d3.event.pageY > 120) show(false); - else if (d3.event.pageY < 60) show(true); - } - } - - return header; -}; diff --git a/lib/cube/client/palette.css b/lib/cube/client/palette.css deleted file mode 100644 index 15922b3d..00000000 --- a/lib/cube/client/palette.css +++ /dev/null @@ -1,12 +0,0 @@ -.palette rect { - fill: #eee; - stroke: #999; -} - -.palette rect:hover { - fill: #ddd; -} - -.palette text { - pointer-events: none; -} diff --git a/lib/cube/client/palette.js b/lib/cube/client/palette.js deleted file mode 100644 index 305c90de..00000000 --- a/lib/cube/client/palette.js +++ /dev/null @@ -1,63 +0,0 @@ -cube.palette = function(board) { - var palette = {}; - - var g = document.createElementNS(d3.ns.prefix.svg, "g"); - - var type = d3.select(g) - .attr("class", "palette") - .selectAll(".piece-type") - .data(d3.entries(cube.piece.type)) - .enter().append("svg:g") - .attr("class", "piece-type") - .on("mousedown", mousedown); - - type.append("svg:rect"); - - type.append("svg:text") - .attr("dy", ".35em") - .attr("text-anchor", "middle") - .text(function(d) { return d.key; }); - - board - .on("squareSize", resize) - .on("squareRadius", resize); - - function resize() { - var size = board.squareSize(), - radius = board.squareRadius(); - - type - .attr("transform", function(d, i) { return "translate(" + (i * size + size / 2) + "," + (size / 2) + ")"; }) - .select("rect") - .attr("x", -size / 2) - .attr("y", -size / 2) - .attr("width", size) - .attr("height", size) - .attr("rx", radius) - .attr("ry", radius); - } - - function mousedown(d) { - var piece = board.add(d.value), - pieceSize = piece.size(), - squareSize = board.squareSize(), - mouse = d3.svg.mouse(g); - - piece.position([ - mouse[0] / squareSize - pieceSize[0] / 2, - mouse[1] / squareSize - pieceSize[1] / 2 - 1.5 - ]); - - // Simulate mousedown on the piece to start dragging. - var div = d3.select(piece.node()); - div.each(div.on("mousedown.piece")); - } - - palette.node = function() { - return g; - }; - - resize(); - - return palette; -}; diff --git a/lib/cube/client/piece-area.js b/lib/cube/client/piece-area.js deleted file mode 100644 index 6e7c257a..00000000 --- a/lib/cube/client/piece-area.js +++ /dev/null @@ -1,255 +0,0 @@ -cube.piece.type.area = function(board) { - var timeout, - data = [], - dt0; - - var area = cube.piece(board) - .on("size", resize) - .on("serialize", serialize) - .on("deserialize", deserialize); - - var div = d3.select(area.node()) - .classed("area", true); - - if (mode == "edit") { - div.append("h3") - .attr("class", "title") - .text("Area Chart"); - - var query = div.append("textarea") - .attr("class", "query") - .attr("placeholder", "query expression…") - .on("keyup.area", querychange) - .on("focus.area", area.focus) - .on("blur.area", area.blur); - - var time = div.append("div") - .attr("class", "time") - .text("Time Range:"); - - time.append("input") - .property("value", 1440); - - time.append("select").selectAll("option") - .data([ - {description: "Seconds @ 10", value: 1e4}, - {description: "Minutes @ 5", value: 3e5}, - {description: "Hours", value: 36e5}, - {description: "Days", value: 864e5}, - {description: "Weeks", value: 6048e5}, - {description: "Months", value: 2592e6} - ]) - .enter().append("option") - .property("selected", function(d, i) { return i == 1; }) - .attr("value", cube_piece_areaValue) - .text(function(d) { return d.description; }); - - time.selectAll("input,select") - .on("change.area", area.edit) - .on("focus.area", area.focus) - .on("blur.area", area.blur) - } else { - var m = [6, 40, 14, 10], // top, right, bottom, left margins - socket; - - var svg = div.append("svg:svg"); - - var x = d3.time.scale(), - y = d3.scale.linear(), - xAxis = d3.svg.axis().scale(x).orient("bottom").tickSubdivide(true), - yAxis = d3.svg.axis().scale(y).orient("right"); - - var a = d3.svg.area() - .interpolate("step-after") - .x(function(d) { return x(d.time); }) - .y0(function(d) { return y(0); }) - .y1(function(d) { return y(d.value); }); - - var l = d3.svg.line() - .interpolate("step-after") - .x(function(d) { return x(d.time); }) - .y(function(d) { return y(d.value); }); - - var g = svg.append("svg:g") - .attr("transform", "translate(" + m[3] + "," + m[0] + ")"); - - g.append("svg:g").attr("class", "y axis").call(yAxis); - g.append("svg:path").attr("class", "area"); - g.append("svg:g").attr("class", "x axis").call(xAxis); - g.append("svg:path").attr("class", "line"); - } - - function resize() { - var transition = area.transition(); - - if (mode == "edit") { - var innerSize = area.innerSize(); - - transition.select(".query") - .style("width", innerSize[0] - 12 + "px") - .style("height", innerSize[1] - 58 + "px"); - - transition.select(".time select") - .style("width", innerSize[0] - 174 + "px"); - - } else { - var z = board.squareSize(), - w = area.size()[0] * z - m[1] - m[3], - h = area.size()[1] * z - m[0] - m[2]; - - x.range([0, w]); - y.range([h, 0]); - - // Adjust the ticks based on the current chart dimensions. - xAxis.ticks(w / 80).tickSize(-h, 0); - yAxis.ticks(h / 25).tickSize(-w, 0); - - transition.select("svg") - .attr("width", w + m[1] + m[3]) - .attr("height", h + m[0] + m[2]); - - transition.select(".area") - .attr("d", a(data)); - - transition.select(".x.axis") - .attr("transform", "translate(0," + h + ")") - .call(xAxis) - .select("path") - .attr("transform", "translate(0," + (y(0) - h) + ")"); - - transition.select(".y.axis") - .attr("transform", "translate(" + w + ",0)") - .call(yAxis); - - transition.select(".line") - .attr("d", l(data)); - } - } - - function redraw() { - if (data.length > 1) data[data.length - 1].value = data[data.length - 2].value; - - var z = board.squareSize(), - h = area.size()[1] * z - m[0] - m[2], - min = d3.min(data, cube_piece_areaValue), - max = d3.max(data, cube_piece_areaValue); - - if ((min < 0) && (max < 0)) max = 0; - else if ((min > 0) && (max > 0)) min = 0; - y.domain([min, max]).nice(); - - div.select(".area").attr("d", a(data)); - div.select(".y.axis").call(yAxis.tickFormat(cube_piece_format(y.domain()))); - div.select(".x.axis").call(xAxis).select("path").attr("transform", "translate(0," + (y(0) - h) + ")"); - div.select(".line").attr("d", l(data)); - return true; - } - - function querychange() { - if (timeout) clearTimeout(timeout); - timeout = setTimeout(area.edit, 750); - } - - function serialize(json) { - var step = +time.select("select").property("value"), - range = time.select("input").property("value") * cube_piece_areaMultipler(step); - json.type = "area"; - json.query = query.property("value"); - json.time = {range: range, step: step}; - } - - function deserialize(json) { - if (mode == "edit") { - query.property("value", json.query); - time.select("input").property("value", json.time.range / cube_piece_areaMultipler(json.time.step)); - time.select("select").property("value", json.time.step); - } else { - var dt1 = json.time.step, - t1 = new Date(Math.floor(Date.now() / dt1) * dt1), - t0 = new Date(t1 - json.time.range), - d0 = x.domain(), - d1 = [t0, t1]; - - if (dt0 != dt1) { - data = []; - dt0 = dt1; - } - - if (d0 != d1 + "") { - x.domain(d1); - resize(); - var times = data.map(cube_piece_areaTime); - data = data.slice(d3.bisectLeft(times, t0), d3.bisectLeft(times, t1)); - data.push({time: t1, value: 0}); - } - - if (timeout) timeout = clearTimeout(timeout); - if (socket) socket.close(); - socket = new WebSocket("ws://" + location.host + "/1.0/metric/get"); - socket.onopen = load; - socket.onmessage = store; - - function load() { - timeout = setTimeout(function() { - socket.send(JSON.stringify({ - expression: json.query, - start: cube_time(t0), - stop: cube_time(t1), - step: dt1 - })); - timeout = setTimeout(function() { - deserialize(json); - }, t1 - Date.now() + dt1 + 4500 + 1000 * Math.random()); - }, 500); - } - - // TODO use a skip list to insert more efficiently - // TODO compute contiguous segments on the fly - function store(message) { - var d = JSON.parse(message.data); - var i = d3.bisectLeft(data.map(cube_piece_areaTime), d.time = cube_time.parse(d.time)); - if (i < 0 || data[i].time - d.time) { - if (d.value != null) { - data.splice(i, 0, d); - } - } else if (d.value == null) { - data.splice(i, 1); - } else { - data[i] = d; - } - d3.timer(redraw); - } - } - } - - area.copy = function() { - return board.add(cube.piece.type.area); - }; - - resize(); - - return area; -}; - -function cube_piece_areaTime(d) { - return d.time; -} - -function cube_piece_areaValue(d) { - return d.value; -} - -var cube_piece_formatNumber = d3.format(".2r"); - -function cube_piece_areaMultipler(step) { - return step / (step === 1e4 ? 10 - : step === 3e5 ? 5 - : 1); -} - -function cube_piece_format(domain) { - var prefix = d3.formatPrefix(Math.max(-domain[0], domain[1]), 2); - return function(value) { - return cube_piece_formatNumber(value * prefix.scale) + prefix.symbol; - }; -} diff --git a/lib/cube/client/piece-sum.js b/lib/cube/client/piece-sum.js deleted file mode 100644 index 900a4cba..00000000 --- a/lib/cube/client/piece-sum.js +++ /dev/null @@ -1,137 +0,0 @@ -cube.piece.type.sum = function(board) { - var timeout, - socket, - data = 0, - format = d3.format(".2s"); - - var sum = cube.piece(board) - .on("size", resize) - .on("serialize", serialize) - .on("deserialize", deserialize); - - var div = d3.select(sum.node()) - .classed("sum", true); - - if (mode == "edit") { - div.append("h3") - .attr("class", "title") - .text("Rolling Sum"); - - var query = div.append("textarea") - .attr("class", "query") - .attr("placeholder", "query expression…") - .on("keyup.sum", querychange) - .on("focus.sum", sum.focus) - .on("blur.sum", sum.blur); - - var time = div.append("div") - .attr("class", "time") - .text("Time Range:"); - - time.append("input") - .property("value", 1440); - - time.append("select").selectAll("option") - .data([ - {description: "Seconds @ 10", value: 1e4}, - {description: "Minutes @ 5", value: 3e5}, - {description: "Hours", value: 36e5}, - {description: "Days", value: 864e5}, - {description: "Weeks", value: 6048e5}, - {description: "Months", value: 2592e6} - ]) - .enter().append("option") - .property("selected", function(d, i) { return i == 1; }) - .attr("value", cube_piece_areaValue) - .text(function(d) { return d.description; }); - - time.selectAll("input,select") - .on("change.sum", sum.edit) - .on("focus.sum", sum.focus) - .on("blur.sum", sum.blur) - } - - function resize() { - var innerSize = sum.innerSize(), - transition = sum.transition(); - - if (mode == "edit") { - transition.select(".query") - .style("width", innerSize[0] - 12 + "px") - .style("height", innerSize[1] - 58 + "px"); - - transition.select(".time select") - .style("width", innerSize[0] - 174 + "px"); - } else { - transition - .style("font-size", innerSize[0] / 5 + "px") - .style("line-height", innerSize[1] + "px") - .text(format(data)); - } - } - - function redraw() { - div.text(format(data)); - return true; - } - - function querychange() { - if (timeout) clearTimeout(timeout); - timeout = setTimeout(sum.edit, 750); - } - - function serialize(json) { - var step = +time.select("select").property("value"), - range = time.select("input").property("value") * cube_piece_areaMultipler(step); - json.type = "sum"; - json.query = query.property("value"); - json.time = {range: range, step: step}; - } - - function deserialize(json) { - if (!json.time.range) json.time = {range: json.time, step: 3e5}; - if (mode == "edit") { - query.property("value", json.query); - time.select("input").property("value", json.time.range / cube_piece_areaMultipler(json.time.step)); - time.select("select").property("value", json.time.step); - } else { - var dt = json.time.step, - t1 = new Date(Math.floor(Date.now() / dt) * dt), - t0 = new Date(t1 - json.time.range); - - data = 0; - - if (timeout) timeout = clearTimeout(timeout); - if (socket) socket.close(); - socket = new WebSocket("ws://" + location.host + "/1.0/metric/get"); - socket.onopen = load; - socket.onmessage = store; - - function load() { - socket.send(JSON.stringify({ - expression: json.query, - start: cube_time(t0), - stop: cube_time(t1), - step: dt - })); - timeout = setTimeout(function() { - deserialize(json); - }, t1 - Date.now() + dt + 4500 + 1000 * Math.random()); - } - - function store(message) { - var d = JSON.parse(message.data); - if (!isNaN(d.value)) data += d.value; - d3.timer(redraw); - } - } - } - - sum.copy = function() { - return board.add(cube.piece.type.sum); - }; - - resize(); - - return sum; -}; diff --git a/lib/cube/client/piece-text.js b/lib/cube/client/piece-text.js deleted file mode 100644 index 857e75e3..00000000 --- a/lib/cube/client/piece-text.js +++ /dev/null @@ -1,65 +0,0 @@ -cube.piece.type.text = function(board) { - var timeout; - - var text = cube.piece(board) - .on("size", resize) - .on("serialize", serialize) - .on("deserialize", deserialize); - - var div = d3.select(text.node()) - .classed("text", true); - - if (mode == "edit") { - div.append("h3") - .attr("class", "title") - .text("Text Label"); - - var content = div.append("textarea") - .attr("class", "content") - .attr("placeholder", "text content…") - .on("keyup.text", textchange) - .on("focus.text", text.focus) - .on("blur.text", text.blur); - } - - function resize() { - var transition = text.transition(), - innerSize = text.innerSize(); - - if (mode == "edit") { - transition.select(".content") - .style("width", innerSize[0] - 12 + "px") - .style("height", innerSize[1] - 34 + "px"); - } else { - transition - .style("font-size", innerSize[0] / 12 + "px") - .style("margin-top", innerSize[1] / 2 + innerSize[0] / 5 - innerSize[0] / 12 + "px"); - } - } - - function textchange() { - if (timeout) clearTimeout(timeout); - timeout = setTimeout(text.edit, 750); - } - - function serialize(json) { - json.type = "text"; - json.content = content.property("value"); - } - - function deserialize(json) { - if (mode == "edit") { - content.property("value", json.content); - } else { - div.text(json.content); - } - } - - text.copy = function() { - return board.add(cube.piece.type.text); - }; - - resize(); - - return text; -}; diff --git a/lib/cube/client/piece.css b/lib/cube/client/piece.css deleted file mode 100644 index c3e423ed..00000000 --- a/lib/cube/client/piece.css +++ /dev/null @@ -1,151 +0,0 @@ -.piece { - position: absolute; - text-shadow: 0 1px 1px #fff; - font-size: 12px; -} - -.view .piece { - font: 10px sans-serif; - padding: 4px 4px 3px 3px; -} - -.edit .piece { - background: #eee; - border: solid 2px #ccc; - border-radius: 4px; - margin: -2px 0 0 -2px; -} - -.piece:focus, .piece.active { - box-shadow: 0 4px 8px rgba(0,0,0,.3); - outline-style: none; - border-color: #999; - background-color: #e7e7e7; -} - -.piece h3 { - pointer-events: none; - font-size: 14px; - margin: 4px; -} - -.piece textarea, .piece input { - margin-left: 4px; - height: 20px; - border-radius: 4px; - border: solid 1px #ccc; - resize: none; -} - -.piece .time { - margin-top: 1px; - margin-left: 4px; - line-height: 20px; -} - -.piece input { - height: 16px; - width: 88px; -} - -.piece select { - margin-right: 4px; - float: right; - height: 20px; - border-radius: 4px; - border: solid 1px #ccc; -} - -.view .piece.sum, .view .piece.text { - text-align: right; -} - -.resize { - position: absolute; - padding: 6px; -} - -.resize.n { - top: -8px; - left: -8px; - width: 100%; - cursor: ns-resize; -} - -.resize.e { - top: -8px; - right: -8px; - height: 100%; - cursor: ew-resize; -} - -.resize.s { - bottom: -8px; - left: -8px; - width: 100%; - cursor: ns-resize; -} - -.resize.w { - top: -8px; - left: -8px; - height: 100%; - cursor: ew-resize; -} - -.resize.nw { - top: -8px; - left: -8px; - cursor: nwse-resize; -} - -.resize.ne { - top: -8px; - right: -8px; - cursor: nesw-resize; -} - -.resize.se { - bottom: -8px; - right: -8px; - cursor: nwse-resize; -} - -.resize.sw { - bottom: -8px; - left: -8px; - cursor: nesw-resize; -} - -.area path.area { - fill: #e7e7e7; -} - -.area path.line { - fill: none; - stroke: #666; -} - -.axis { - shape-rendering: crispEdges; -} - -.axis .domain { - fill: none; -} - -.x.axis .domain { - stroke: #000; -} - -.x.axis line { - stroke: #fff; -} - -.x.axis .minor { - stroke-opacity: .5; -} - -.y.axis line { - stroke: #eee; -} diff --git a/lib/cube/client/piece.js b/lib/cube/client/piece.js deleted file mode 100644 index 99f4ac1f..00000000 --- a/lib/cube/client/piece.js +++ /dev/null @@ -1,270 +0,0 @@ -cube.piece = function(board) { - var piece = {}, - size = [8, 3], - position = [0, 0], - padding = 4; - - var event = d3.dispatch( - "position", - "size", - "move", - "edit", - "serialize", - "deserialize" - ); - - var div = document.createElement("div"), - selection = d3.select(div), - transition = selection; - - var selection = d3.select(div) - .attr("class", "piece"); - - if (mode == "edit") { - selection - .attr("tabindex", 1) - .on("keydown.piece", keydown) - .on("mousedown.piece", mousedrag) - .selectAll(".resize") - .data(["n", "e", "s", "w", "nw", "ne", "se", "sw"]) - .enter().append("div") - .attr("class", function(d) { return "resize " + d; }) - .on("mousedown.piece", mouseresize); - - d3.select(window) - .on("keydown.piece", cube_piece_keydown) - .on("mousemove.piece", cube_piece_mousemove) - .on("mouseup.piece", cube_piece_mouseup); - } - - board - .on("padding.piece", resize) - .on("squareSize.piece", resize); - - event.on("position.piece", resize); - event.on("size.piece", resize); - event.on("deserialize.piece", deserialize); - - function resize() { - var squareSize = board.squareSize(), - boardPadding = board.padding() | 0; - - piece.transition() - .style("left", (padding + boardPadding + position[0] * squareSize) + "px") - .style("top", (padding + boardPadding + (1.5 + position[1]) * squareSize) + "px") - .style("width", size[0] * squareSize - 2 * padding + 1 + "px") - .style("height", size[1] * squareSize - 2 * padding + 1 + "px"); - } - - function deserialize(x) { - piece - .size(x.size) - .position(x.position); - } - - function keydown() { - if (d3.event.target !== this) return d3.event.stopPropagation(); - if (cube_piece_dragPiece) return; - if (d3.event.keyCode === 8) { - board.remove(piece); - d3.event.preventDefault(); - } - } - - piece.node = function() { - return div; - }; - - piece.on = function(type, listener) { - event.on(type, listener); - return piece; - }; - - piece.size = function(x) { - if (!arguments.length) return size; - event.size.call(piece, size = x); - return piece; - }; - - piece.innerSize = function() { - var squareSize = board.squareSize(); - return [ - size[0] * squareSize - 2 * padding, - size[1] * squareSize - 2 * padding - ]; - }; - - piece.position = function(x) { - if (!arguments.length) return position; - event.position.call(piece, position = x); - return piece; - }; - - piece.toJSON = function() { - var x = {id: piece.id, size: size, position: position}; - event.serialize.call(piece, x); - return x; - }; - - piece.fromJSON = function(x) { - event.deserialize.call(piece, x); - return piece; - }; - - piece.edit = function() { - event.edit.call(piece); - return piece; - }; - - function mousedrag() { - if (/^(INPUT|TEXTAREA|SELECT)$/.test(d3.event.target.tagName)) return; - if (d3.event.target === this && d3.event.altKey) { - cube_piece_dragPiece = piece.copy().fromJSON(piece.toJSON()); - cube_piece_dragPiece.node().focus(); - } else { - d3.select(this).transition(); // cancel transition, if any - this.parentNode.appendChild(this).focus(); - cube_piece_dragPiece = piece; - } - cube_piece_dragOrigin = [d3.event.pageX, d3.event.pageY]; - cube_piece_dragPosition = position.slice(); - cube_piece_dragSize = size.slice(); - cube_piece_dragBoard = board; - cube_piece_mousemove(); - } - - function mouseresize(d) { - cube_piece_dragResize = d; - } - - piece.transition = function(x) { - if (!arguments.length) return transition; - if (x == null) { - transition = selection; - } else { - transition = x.select(function() { return div; }); - d3.timer(function() { - event.move.call(piece); - return transition = selection; - }); - } - return piece; - }; - - piece.focus = function() { - selection.classed("active", true); - return piece; - }; - - piece.blur = function() { - selection.classed("active", false); - return piece; - }; - - resize(); - - return piece; -}; - -cube.piece.type = {}; - -var cube_piece_dragPiece, - cube_piece_dragBoard, - cube_piece_dragOrigin, - cube_piece_dragPosition, - cube_piece_dragSize, - cube_piece_dragResize; - -function cube_piece_mousePosition() { - var squareSize = cube_piece_dragBoard.squareSize(); - return cube_piece_dragResize ? [ - cube_piece_dragPosition[0] + /w$/.test(cube_piece_dragResize) * Math.min(cube_piece_dragSize[0] - 5, (d3.event.pageX - cube_piece_dragOrigin[0]) / squareSize), - cube_piece_dragPosition[1] + /^n/.test(cube_piece_dragResize) * Math.min(cube_piece_dragSize[1] - 3, (d3.event.pageY - cube_piece_dragOrigin[1]) / squareSize) - ] : [ - cube_piece_dragPosition[0] + (d3.event.pageX - cube_piece_dragOrigin[0]) / squareSize, - cube_piece_dragPosition[1] + (d3.event.pageY - cube_piece_dragOrigin[1]) / squareSize - ]; -} - -function cube_piece_mouseSize() { - var squareSize = cube_piece_dragBoard.squareSize(); - return cube_piece_dragResize ? [ - Math.max(5, cube_piece_dragSize[0] + (/e$/.test(cube_piece_dragResize) ? 1 : /w$/.test(cube_piece_dragResize) ? -1 : 0) * (d3.event.pageX - cube_piece_dragOrigin[0]) / squareSize), - Math.max(3, cube_piece_dragSize[1] + (/^s/.test(cube_piece_dragResize) ? 1 : /^n/.test(cube_piece_dragResize) ? -1 : 0) * (d3.event.pageY - cube_piece_dragOrigin[1]) / squareSize) - ] : cube_piece_dragSize; -} - -function cube_piece_clamp(position, size) { - var boardSize = cube_piece_dragBoard.size(); - if (cube_piece_dragResize) { - if (/e$/.test(cube_piece_dragResize)) { - size[0] = Math.max(0, Math.min(boardSize[0] - position[0], Math.round(size[0]))); - } else if (/w$/.test(cube_piece_dragResize)) { - size[0] = Math.round(size[0] + position[0] - (position[0] = Math.max(0, Math.min(boardSize[0], Math.round(position[0]))))); - } - if (/^s/.test(cube_piece_dragResize)) { - size[1] = Math.max(0, Math.min(boardSize[1] - position[1], Math.round(size[1]))); - } else if (/^n/.test(cube_piece_dragResize)) { - size[1] = Math.round(size[1] + position[1] - (position[1] = Math.max(0, Math.min(boardSize[1], Math.round(position[1]))))); - } - } else { - position[0] = Math.max(0, Math.min(boardSize[0] - size[0], Math.round(position[0]))); - position[1] = Math.max(0, Math.min(boardSize[1] - size[1], Math.round(position[1]))); - } -} - -function cube_piece_mousemove() { - if (cube_piece_dragPiece) { - var position = cube_piece_mousePosition(), - size = cube_piece_mouseSize(); - - cube_piece_dragPiece - .position(position.slice()) - .size(size.slice()); - - cube_piece_clamp(position, size); - - var x0 = position[0], - y0 = position[1], - x1 = x0 + size[0], - y1 = y0 + size[1]; - - d3.select(cube_piece_dragBoard.node()).selectAll(".squares rect") - .classed("shadow", function(d, i) { return d.x >= x0 && d.x < x1 && d.y >= y0 && d.y < y1; }); - - d3.event.preventDefault(); - } -} - -function cube_piece_mouseup() { - if (cube_piece_dragPiece) { - var position = cube_piece_mousePosition(), - size = cube_piece_mouseSize(); - - cube_piece_clamp(position, size); - - d3.select(cube_piece_dragBoard.node()).selectAll(".squares rect") - .classed("shadow", false); - - cube_piece_dragPiece - .transition(d3.transition().ease("elastic").duration(500)) - .position(position) - .size(size); - - cube_piece_dragPiece = - cube_piece_dragBoard = - cube_piece_dragEvent = - cube_piece_dragOrigin = - cube_piece_dragPosition = - cube_piece_dragSize = - cube_piece_dragResize = null; - d3.event.preventDefault(); - } -} - -// Disable delete as the back key, since we use it to delete pieces. -function cube_piece_keydown() { - if (d3.event.keyCode == 8) { - d3.event.preventDefault(); - } -} diff --git a/lib/cube/client/semicolon.js b/lib/cube/client/semicolon.js deleted file mode 100644 index 1c8a0e79..00000000 --- a/lib/cube/client/semicolon.js +++ /dev/null @@ -1 +0,0 @@ -; \ No newline at end of file diff --git a/lib/cube/client/squares.js b/lib/cube/client/squares.js deleted file mode 100644 index 0c42e4d2..00000000 --- a/lib/cube/client/squares.js +++ /dev/null @@ -1,45 +0,0 @@ -cube.squares = function(board) { - var squares = {}; - - var g = document.createElementNS(d3.ns.prefix.svg, "g"); - - d3.select(g) - .attr("class", "squares"); - - board - .on("size", resize) - .on("squareSize", resize) - .on("squareRadius", resize); - - function resize() { - var boardSize = board.size(), - squareSize = board.squareSize(), - squareRadius = board.squareRadius(); - - var square = d3.select(g).selectAll(".square") - .data(d3.range(boardSize[0] * boardSize[1]) - .map(function(d) {return { x: d % boardSize[0], y: d / boardSize[0] | 0}; })); - - square.enter().append("svg:rect") - .attr("class", "square"); - - square - .attr("rx", squareRadius) - .attr("ry", squareRadius) - .attr("class", function(d, i) { return (i - d.y & 1 ? "black" : "white") + " square"; }) - .attr("x", function(d) { return d.x * squareSize; }) - .attr("y", function(d) { return d.y * squareSize; }) - .attr("width", squareSize) - .attr("height", squareSize); - - square.exit().remove(); - } - - squares.node = function() { - return g; - }; - - resize(); - - return squares; -}; diff --git a/lib/cube/client/start.js b/lib/cube/client/start.js deleted file mode 100644 index af50383e..00000000 --- a/lib/cube/client/start.js +++ /dev/null @@ -1 +0,0 @@ -(function(){ diff --git a/lib/cube/client/visualizer.html b/lib/cube/client/visualizer.html deleted file mode 100644 index 4feacb8d..00000000 --- a/lib/cube/client/visualizer.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - Cube - - - - - - -
- - - diff --git a/lib/cube/server/collectd.js b/lib/cube/collectd.js similarity index 100% rename from lib/cube/server/collectd.js rename to lib/cube/collectd.js diff --git a/lib/cube/collector.js b/lib/cube/collector.js new file mode 100644 index 00000000..fd7d1c23 --- /dev/null +++ b/lib/cube/collector.js @@ -0,0 +1,44 @@ +var endpoint = require("./endpoint"); + +// +var headers = { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*" +}; + +exports.register = function(db, endpoints) { + var putter = require("./event").putter(db), + poster = post(putter); + + // + endpoints.ws.push( + endpoint("/1.0/event/put", putter) + ); + + // + endpoints.http.push( + endpoint("POST", "/1.0/event", poster), + endpoint("POST", "/1.0/event/put", poster), + endpoint("POST", "/collectd", require("./collectd").putter(putter)) + ); +}; + +function post(putter) { + return function(request, response) { + var content = ""; + request.on("data", function(chunk) { + content += chunk; + }); + request.on("end", function() { + try { + JSON.parse(content).forEach(putter); + } catch (e) { + response.writeHead(400, headers); + response.end(JSON.stringify({error: e.toString()})); + return; + } + response.writeHead(200, headers); + response.end("{}"); + }); + }; +} diff --git a/lib/cube/server/emitter.js b/lib/cube/emitter.js similarity index 100% rename from lib/cube/server/emitter.js rename to lib/cube/emitter.js diff --git a/lib/cube/endpoint.js b/lib/cube/endpoint.js new file mode 100644 index 00000000..0e86463e --- /dev/null +++ b/lib/cube/endpoint.js @@ -0,0 +1,8 @@ +module.exports = function(method, path, dispatch) { + return { + match: arguments.length < 3 + ? (dispatch = path, path = method, function(p) { return p == path; }) + : function(p, m) { return m == method && p == path; }, + dispatch: dispatch + }; +} diff --git a/lib/cube/server/evaluator.js b/lib/cube/evaluator.js similarity index 89% rename from lib/cube/server/evaluator.js rename to lib/cube/evaluator.js index ccca00a0..55e8092d 100644 --- a/lib/cube/server/evaluator.js +++ b/lib/cube/evaluator.js @@ -6,6 +6,7 @@ var endpoint = require("./endpoint"), // results are returned. var limitMax = 1e4; +// var headers = { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" @@ -15,15 +16,18 @@ exports.register = function(db, endpoints) { var event = require("./event").getter(db), metric = require("./metric").getter(db); + // endpoints.ws.push( - endpoint.exact("/1.0/event/get", event), - endpoint.exact("/1.0/metric/get", metric) + endpoint("/1.0/event/get", event), + endpoint("/1.0/metric/get", metric) ); + + // endpoints.http.push( - endpoint.exact("GET", "/1.0/event", eventGet), - endpoint.exact("GET", "/1.0/event/get", eventGet), - endpoint.exact("GET", "/1.0/metric", metricGet), - endpoint.exact("GET", "/1.0/metric/get", metricGet) + endpoint("GET", "/1.0/event", eventGet), + endpoint("GET", "/1.0/event/get", eventGet), + endpoint("GET", "/1.0/metric", metricGet), + endpoint("GET", "/1.0/metric/get", metricGet) ); function eventGet(request, response) { diff --git a/lib/cube/server/event-expression.js b/lib/cube/event-expression.js similarity index 100% rename from lib/cube/server/event-expression.js rename to lib/cube/event-expression.js diff --git a/lib/cube/server/event-expression.peg b/lib/cube/event-expression.peg similarity index 100% rename from lib/cube/server/event-expression.peg rename to lib/cube/event-expression.peg diff --git a/lib/cube/server/event.js b/lib/cube/event.js similarity index 100% rename from lib/cube/server/event.js rename to lib/cube/event.js diff --git a/lib/cube/index.js b/lib/cube/index.js index a5fc1249..af67319e 100644 --- a/lib/cube/index.js +++ b/lib/cube/index.js @@ -1,7 +1,5 @@ -exports.version = "0.0.9"; -exports.emitter = require("./server/emitter"); -exports.server = require("./server/server"); -exports.collector = require("./server/collector"); -exports.evaluator = require("./server/evaluator"); -exports.visualizer = require("./server/visualizer"); -exports.endpoint = require("./server/endpoint"); +exports.emitter = require("./emitter"); +exports.server = require("./server"); +exports.collector = require("./collector"); +exports.evaluator = require("./evaluator"); +exports.endpoint = require("./endpoint"); diff --git a/lib/cube/server/metric-expression.js b/lib/cube/metric-expression.js similarity index 100% rename from lib/cube/server/metric-expression.js rename to lib/cube/metric-expression.js diff --git a/lib/cube/server/metric-expression.peg b/lib/cube/metric-expression.peg similarity index 100% rename from lib/cube/server/metric-expression.peg rename to lib/cube/metric-expression.peg diff --git a/lib/cube/server/metric.js b/lib/cube/metric.js similarity index 100% rename from lib/cube/server/metric.js rename to lib/cube/metric.js diff --git a/lib/cube/server/reduces.js b/lib/cube/reduces.js similarity index 100% rename from lib/cube/server/reduces.js rename to lib/cube/reduces.js diff --git a/lib/cube/server/server.js b/lib/cube/server.js similarity index 90% rename from lib/cube/server/server.js rename to lib/cube/server.js index e2259360..452b3582 100644 --- a/lib/cube/server/server.js +++ b/lib/cube/server.js @@ -3,6 +3,7 @@ var util = require("util"), http = require("http"), websocket = require("websocket"), websprocket = require("websocket-server"), + static = require("node-static"), mongodb = require("mongodb"); // Don't crash on errors. @@ -12,7 +13,7 @@ process.on("uncaughtException", function(error) { }); // And then this happened: -websprocket.Connection = require("../../../node_modules/websocket-server/lib/ws/connection"); +websprocket.Connection = require("../../node_modules/websocket-server/lib/ws/connection"); // Configuration for WebSocket requests. var wsOptions = { @@ -35,6 +36,7 @@ module.exports = function(options) { var server = {}, primary = http.createServer(), secondary = websprocket.createServer(), + file = new static.Server("static"), meta, endpoints = {ws: [], http: []}, mongo = new mongodb.Server(options["mongo-host"], options["mongo-port"], server_options), @@ -141,8 +143,15 @@ module.exports = function(options) { } } - response.writeHead(404, {"Content-Type": "text/plain"}); - response.end("404 Not Found"); + // If this request wasn't matched, see if there's a static file to serve. + request.on("end", function() { + file.serve(request, response, function(error) { + if (error && error.status == 404) { + response.writeHead(404, {"Content-Type": "text/plain"}); + response.end("404 Not Found"); + } + }); + }); }); server.start = function() { diff --git a/lib/cube/server/collector.js b/lib/cube/server/collector.js deleted file mode 100644 index a763bcea..00000000 --- a/lib/cube/server/collector.js +++ /dev/null @@ -1,41 +0,0 @@ -var endpoint = require("./endpoint"), - util = require("util"); - -exports.register = function(db, endpoints) { - var putter = require("./event").putter(db), - poster = post(putter); - endpoints.ws.push( - endpoint.exact("/1.0/event/put", putter) - ); - endpoints.http.push( - endpoint.exact("POST", "/1.0/event", poster), - endpoint.exact("POST", "/1.0/event/put", poster), - endpoint.exact("POST", "/collectd", require("./collectd").putter(putter)) - ); -}; - -function post(putter) { - return function(request, response) { - var content = ""; - request.on("data", function(chunk) { - content += chunk; - }); - request.on("end", function() { - try { - JSON.parse(content).forEach(putter); - } catch (e) { - util.log(e); - response.writeHead(400, { - "Content-Type": "application/json", - "Access-Control-Allow-Origin": "*" - }); - return response.end("{\"status\":400}"); - } - response.writeHead(200, { - "Content-Type": "application/json", - "Access-Control-Allow-Origin": "*" - }); - response.end("{\"status\":200}"); - }); - }; -} diff --git a/lib/cube/server/endpoint.js b/lib/cube/server/endpoint.js deleted file mode 100644 index c944ef4c..00000000 --- a/lib/cube/server/endpoint.js +++ /dev/null @@ -1,86 +0,0 @@ -var fs = require("fs"), - url = require("url"), - path = require("path"), - cube = require("../"); - -exports.re = re; -exports.exact = exact; -exports.file = file; - -var types = { - html: "text/html;charset=utf-8", - css: "text/css;charset=utf-8", - js: "text/javascript;charset=utf-8", - json: "application/json;charset=utf-8", - png: "image/png" -}; - -function exact(method, path, dispatch) { - return { - match: arguments.length < 3 - ? (dispatch = path, path = method, function(p) { return p == path; }) - : function(p, m) { return m == method && p == path; }, - dispatch: dispatch - }; -} - -function re(re, dispatch) { - return { - match: function(p) { return re.test(p); }, - dispatch: dispatch - }; -} - -function file() { - var files = Array.prototype.slice.call(arguments), - type = types[files[0].substring(files[0].lastIndexOf(".") + 1)]; - return function(request, response) { - var modified = -Infinity, - size = 0, - n = files.length; - - files.forEach(function(file) { - fs.stat(file, function(error, stats) { - if (error) throw fiveohoh(request, response), error; - size += stats.size; - var time = new Date(stats.mtime); - if (time > modified) modified = time; - if (!--n) respond(); - }); - }); - - function respond() { - var status = modified <= new Date(request.headers["if-modified-since"]) ? 304 : 200, - hasBody = status === 200 && request.method !== "HEAD"; - - var headers = { - "Content-Type": type, - "Date": new Date().toUTCString(), - "Last-Modified": modified.toUTCString() - }; - - if (hasBody) { - headers["Content-Length"] = size; - response.writeHead(status, headers); - read(0); - } else { - response.writeHead(status, headers); - response.end(); - } - } - - function read(i) { - fs.readFile(files[i], function(error, data) { - if (error) throw fiveohoh(request, response), error; - response.write(data); - if (i < files.length - 1) read(i + 1); - else response.end(); - }); - } - }; -}; - -function fiveohoh(request, response) { - response.writeHead(500, {"Content-Type": "text/plain"}); - response.end("500 Server Error"); -} diff --git a/lib/cube/server/visualizer.js b/lib/cube/server/visualizer.js deleted file mode 100644 index 5ad28c5d..00000000 --- a/lib/cube/server/visualizer.js +++ /dev/null @@ -1,191 +0,0 @@ -var url = require("url"), - path = require("path"), - endpoint = require("./endpoint"); - -exports.register = function(db, endpoints) { - endpoints.ws.push( - endpoint.exact("/board", viewBoard(db)) - ); - endpoints.http.push( - endpoint.exact("/", createBoard(db)), - endpoint.re(/^\/[0-9][0-9a-z]{5}(\/edit)?$/, loadBoard(db)), - endpoint.exact("/cube.js", endpoint.file( - resolve("start.js"), - resolve("cube.js"), - resolve("piece.js"), - resolve("piece-area.js"), - resolve("piece-sum.js"), - resolve("piece-text.js"), - resolve("palette.js"), - resolve("squares.js"), - resolve("board.js"), - resolve("header.js"), - resolve("end.js") - )), - endpoint.exact("/cube.css", endpoint.file( - resolve("body.css"), - resolve("palette.css"), - resolve("board.css"), - resolve("piece.css") - )), - endpoint.exact("/d3/d3.js", endpoint.file( - resolve("../../../node_modules/d3/d3.min.js"), - resolve("semicolon.js"), - resolve("../../../node_modules/d3/d3.geo.min.js"), - resolve("semicolon.js"), - resolve("../../../node_modules/d3/d3.geom.min.js"), - resolve("semicolon.js"), - resolve("../../../node_modules/d3/d3.layout.min.js"), - resolve("semicolon.js"), - resolve("../../../node_modules/d3/d3.time.min.js") - )) - ); -}; - -function createBoard(db) { - var boards, max = parseInt("9zzzzy", 36); - - db.collection("boards", function(error, collection) { - boards = collection; - }); - - return function random(request, response) { - var id = (Math.random() * max | 0) + 1; - boards.insert({_id: id}, {safe: true}, function(error) { - if (error) { - if (/^E11000/.test(error.message)) return random(request, response); // duplicate - response.writeHead(500, {"Content-Type": "text/plain"}); - response.end("500 Server Error"); - } else { - id = id.toString(36); - if (id.length < 6) id = new Array(7 - id.length).join("0") + id; - response.writeHead(302, {"Location": "http://" + request.headers["host"] + "/" + id + "/edit"}); - response.end(); - } - }); - }; -} - -function loadBoard(db) { - var boards, - file = endpoint.file(resolve("visualizer.html")); - - db.collection("boards", function(error, collection) { - boards = collection; - }); - - return function random(request, response) { - var id = parseInt(url.parse(request.url).pathname.substring(1), "36"); - boards.findOne({_id: id}, function(error, object) { - if (object == null) { - response.writeHead(404, {"Content-Type": "text/plain"}); - response.end("404 Not Found"); - } else { - file(request, response); - } - }); - }; -} - -function viewBoard(db) { - var boards, - boardsByCallback = {}, - callbacksByBoard = {}; - - db.collection("boards", function(error, collection) { - boards = collection; - }); - - function dispatch(request, callback) { - switch (request.type) { - case "load": load(request, callback); break; - case "add": add(request, callback); break; - case "edit": case "move": move(request, callback); break; - case "remove": remove(request, callback); break; - default: callback({type: "error", status: 400}); break; - } - } - - function add(request, callback) { - var boardId = request.id, - callbacks = callbacksByBoard[boardId].filter(function(c) { return c.id != callback.id; }); - boards.update({_id: boardId}, {$push: {pieces: request.piece}}); - if (callbacks.length) emit(callbacks, {type: "add", piece: request.piece}); - } - - function move(request, callback) { - var boardId = request.id, - callbacks = callbacksByBoard[boardId].filter(function(c) { return c.id != callback.id; }); - boards.update({_id: boardId, "pieces.id": request.piece.id}, {$set: {"pieces.$": request.piece}}); - if (callbacks.length) emit(callbacks, {type: request.type, piece: request.piece}); - } - - function remove(request, callback) { - var boardId = request.id, - callbacks = callbacksByBoard[boardId].filter(function(c) { return c.id != callback.id; }); - boards.update({_id: boardId}, {$pull: {pieces: {id: request.piece.id}}}); - if (callbacks.length) emit(callbacks, {type: "remove", piece: {id: request.piece.id}}); - } - - function load(request, callback) { - var boardId = boardsByCallback[callback.id], - callbacks; - - // If callback was previously viewing to a different board, remove it. - if (boardId) { - callbacks = callbacksByBoard[boardId]; - callbacks.splice(callbacks.indexOf(callback), 1); - if (callbacks.length) emit(callbacks, {type: "view", count: callbacks.length}); - else delete callbacksByBoard[boardId]; - } - - // Register that we are now viewing the new board. - boardsByCallback[callback.id] = boardId = request.id; - - // If this board has other viewers, notify them. - if (boardId in callbacksByBoard) { - callbacks = callbacksByBoard[boardId]; - callbacks.push(callback); - emit(callbacks, {type: "view", count: callbacks.length}); - } else { - callbacks = callbacksByBoard[boardId] = [callback]; - } - - // Asynchronously load the requested board. - boards.findOne({_id: boardId}, function(error, board) { - if (board != null) { - if (board.pieces) board.pieces.forEach(function(piece) { - callback({type: "add", piece: piece}); - }); - } else { - callback({type: "error", status: 404}); - } - }); - } - - dispatch.close = function(callback) { - var boardId = boardsByCallback[callback.id], - callbacks; - - // If callback was viewing, remove it. - if (boardId) { - callbacks = callbacksByBoard[boardId]; - callbacks.splice(callbacks.indexOf(callback), 1); - if (callbacks.length) emit(callbacks, {type: "view", count: callbacks.length}); - else delete callbacksByBoard[boardId]; - delete boardsByCallback[callback.id]; - } - }; - - return dispatch; -} - -function resolve(file) { - return path.join(__dirname, "../client", file); -} - -function emit(callbacks, event) { - callbacks.forEach(function(callback) { - callback(event); - }); -} diff --git a/lib/cube/server/tiers.js b/lib/cube/tiers.js similarity index 61% rename from lib/cube/server/tiers.js rename to lib/cube/tiers.js index 500ae631..f498220a 100644 --- a/lib/cube/server/tiers.js +++ b/lib/cube/tiers.js @@ -5,10 +5,7 @@ var second = 1000, minute = 60 * second, minute5 = 5 * minute, hour = 60 * minute, - day = 24 * hour, - week = 7 * day, - month = 30 * day, - year = 365 * day; + day = 24 * hour; tiers[second10] = { key: second10, @@ -17,6 +14,13 @@ tiers[second10] = { step: function(d) { return new Date(+d + second10); } }; +tiers[minute] = { + key: minute, + floor: function(d) { return new Date(Math.floor(d / minute) * minute); }, + ceil: tier_ceil, + step: function(d) { return new Date(+d + minute); } +}; + tiers[minute5] = { key: minute5, floor: function(d) { return new Date(Math.floor(d / minute5) * minute5); }, @@ -42,24 +46,6 @@ tiers[day] = { size: function() { return 24; } }; -tiers[week] = { - key: week, - floor: function(d) { (d = new Date(Math.floor(d / day) * day)).setUTCDate(d.getUTCDate() - d.getUTCDay()); return d; }, - ceil: tier_ceil, - step: function(d) { return new Date(+d + week); }, - next: tiers[day], - size: function() { return 7; } -}; - -tiers[month] = { - key: month, - floor: function(d) { return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1)); }, - ceil: tier_ceil, - step: function(d) { (d = new Date(d)).setUTCMonth(d.getUTCMonth() + 1); return d; }, - next: tiers[day], - size: function(d) { return 32 - new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 32)).getUTCDate(); } -}; - function tier_ceil(date) { return this.step(this.floor(new Date(date - 1))); } diff --git a/lib/cube/server/types.js b/lib/cube/types.js similarity index 100% rename from lib/cube/server/types.js rename to lib/cube/types.js diff --git a/package.json b/package.json index 0f372787..4771bb75 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cube", - "version": "0.1.4", + "version": "0.2.0", "description": "A system for time series visualization using MongoDB, Node and D3.", "keywords": ["time series", "visualization"], "homepage": "http://square.github.com/cube/", @@ -8,7 +8,7 @@ "repository": {"type": "git", "url": "http://github.com/square/cube.git"}, "main": "./lib/cube", "dependencies": { - "d3": "2.7.2", + "d3": "2.9.0", "mongodb": "0.9.9-7", "pegjs": "0.6.2", "vows": "0.5.11", diff --git a/test/collector-test.js b/test/collector-test.js index 3195b227..35ba8dc4 100644 --- a/test/collector-test.js +++ b/test/collector-test.js @@ -28,7 +28,7 @@ suite.addBatch(test.batch({ topic: test.request({method: "POST", port: port, path: "/1.0/event/put"}, "This ain't JSON.\n"), "responds with status 400": function(response) { assert.equal(response.statusCode, 400); - assert.deepEqual(JSON.parse(response.body), {status: 400}); + assert.deepEqual(JSON.parse(response.body), {error: "SyntaxError: Unexpected token T"}); } } })); @@ -38,7 +38,7 @@ suite.addBatch(test.batch({ topic: test.request({method: "POST", port: port, path: "/1.0/event/put"}, JSON.stringify(obj)), "responds with status 400": function(response) { assert.equal(response.statusCode, 400); - assert.deepEqual(JSON.parse(response.body), {status: 400}); + assert.deepEqual(JSON.parse(response.body), {error: "TypeError: Object # has no method 'forEach'"}); } } })); @@ -48,7 +48,7 @@ suite.addBatch(test.batch({ topic: test.request({method: "POST", port: port, path: "/1.0/event/put"}, JSON.stringify(arr)), "responds with status 200": function(response) { assert.equal(response.statusCode, 200); - assert.deepEqual(JSON.parse(response.body), {status: 200}); + assert.deepEqual(JSON.parse(response.body), {}); } } })); @@ -58,7 +58,7 @@ suite.addBatch(test.batch({ topic: test.request({method: "POST", port: port, path: "/1.0/event/put"}, JSON.stringify(num)), "responds with status 400": function(response) { assert.equal(response.statusCode, 400); - assert.deepEqual(JSON.parse(response.body), {status: 400}); + assert.deepEqual(JSON.parse(response.body), {error: "TypeError: Object 42 has no method 'forEach'"}); } } })); diff --git a/test/endpoint-test.js b/test/endpoint-test.js deleted file mode 100644 index d1dd8685..00000000 --- a/test/endpoint-test.js +++ /dev/null @@ -1,65 +0,0 @@ -var vows = require("vows"), - assert = require("assert"), - http = require("http"), - test = require("./test"), - endpoint = require("../lib/cube/server/endpoint"); - -var suite = vows.describe("endpoint"); - -var file = "lib/cube/client/semicolon.js", - port = ++test.port, - server = http.createServer(endpoint.file(file, file)); - -server.listen(port, "127.0.0.1"); - -suite.addBatch({ - "file": { - "GET": { - topic: test.request({method: "GET", port: port}), - "the status should be 200": function(response) { - assert.equal(response.statusCode, 200); - }, - "the expected headers should be set": function(response) { - assert.equal(response.headers["content-type"], "text/javascript;charset=utf-8"); - assert.equal(response.headers["content-length"], 2); - assert.ok(new Date(response.headers["date"]) > Date.UTC(2011, 0, 1)); - assert.ok(new Date(response.headers["last-modified"]) > Date.UTC(2011, 0, 1)); - }, - "the expected content should be returned": function(response) { - assert.equal(response.body, ";;"); - } - }, - "GET If-Modified-Since": { - topic: test.request({method: "GET", port: port, headers: {"if-modified-since": new Date(2101, 0, 1).toUTCString()}}), - "the status should be 304": function(response) { - assert.equal(response.statusCode, 304); - }, - "the expected headers should be set": function(response) { - assert.equal(response.headers["content-type"], "text/javascript;charset=utf-8"); - assert.ok(!("Content-Length" in response.headers)); - assert.ok(new Date(response.headers["date"]) > Date.UTC(2011, 0, 1)); - assert.ok(new Date(response.headers["last-modified"]) > Date.UTC(2011, 0, 1)); - }, - "no content should be returned": function(response) { - assert.equal(response.body, ""); - } - }, - "HEAD": { - topic: test.request({method: "HEAD", port: port, headers: {"if-modified-since": new Date(2001, 0, 1).toUTCString()}}), - "the status should be 200": function(response) { - assert.equal(response.statusCode, 200); - }, - "the expected headers should be set": function(response) { - assert.equal(response.headers["content-type"], "text/javascript;charset=utf-8"); - assert.ok(!("Content-Length" in response.headers)); - assert.ok(new Date(response.headers["date"]) > Date.UTC(2011, 0, 1)); - assert.ok(new Date(response.headers["last-modified"]) > Date.UTC(2011, 0, 1)); - }, - "no content should be returned": function(response) { - assert.equal(response.body, ""); - } - } - } -}); - -suite.export(module); diff --git a/test/event-expression-test.js b/test/event-expression-test.js index 44726954..d0cfc919 100644 --- a/test/event-expression-test.js +++ b/test/event-expression-test.js @@ -1,6 +1,6 @@ var vows = require("vows"), assert = require("assert"), - parser = require("../lib/cube/server/event-expression"); + parser = require("../lib/cube/event-expression"); var suite = vows.describe("event-expression"); diff --git a/test/metric-expression-test.js b/test/metric-expression-test.js index 7435a45d..a4d91fa2 100644 --- a/test/metric-expression-test.js +++ b/test/metric-expression-test.js @@ -1,6 +1,6 @@ var vows = require("vows"), assert = require("assert"), - parser = require("../lib/cube/server/metric-expression"); + parser = require("../lib/cube/metric-expression"); var suite = vows.describe("metric-expression"); diff --git a/test/metric-test.js b/test/metric-test.js index 5a4f7e8a..c626f119 100644 --- a/test/metric-test.js +++ b/test/metric-test.js @@ -1,24 +1,24 @@ var vows = require("vows"), assert = require("assert"), test = require("./test"), - event = require("../lib/cube/server/event"), - metric = require("../lib/cube/server/metric"); + event = require("../lib/cube/event"), + metric = require("../lib/cube/metric"); var suite = vows.describe("metric"); var steps = { + 1e4: function(date, n) { return new Date((Math.floor(date / 1e4) + n) * 1e4); }, + 6e4: function(date, n) { return new Date((Math.floor(date / 6e4) + n) * 6e4); }, 3e5: function(date, n) { return new Date((Math.floor(date / 3e5) + n) * 3e5); }, 36e5: function(date, n) { return new Date((Math.floor(date / 36e5) + n) * 36e5); }, - 864e5: function(date, n) { return new Date((Math.floor(date / 864e5) + n) * 864e5); }, - 6048e5: function(date, n) { (date = steps[864e5](date, n * 7)).setUTCDate(date.getUTCDate() - date.getUTCDay()); return date; }, - 2592e6: function(date, n) { return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + n, 1)); } + 864e5: function(date, n) { return new Date((Math.floor(date / 864e5) + n) * 864e5); } }; +steps[1e4].description = "10-second"; +steps[6e4].description = "1-minute"; steps[3e5].description = "5-minute"; steps[36e5].description = "1-hour"; steps[864e5].description = "1-day"; -steps[6048e5].description = "1-week"; -steps[2592e6].description = "1-month"; suite.addBatch(test.batch({ topic: function(test) { @@ -42,11 +42,10 @@ suite.addBatch(test.batch({ start: "2011-07-17T23:47:00.000Z", stop: "2011-07-18T00:50:00.000Z", }, { + 6e4: [0, 0, 0, 1, 1, 3, 5, 7, 9, 11, 13, 15, 17, 39, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 3e5: [0, 17, 65, 143, 175, 225, 275, 325, 375, 425, 475, 0, 0], 36e5: [82, 2418], - 864e5: [82, 2418], - 6048e5: [2500], - 2592e6: [2500] + 864e5: [82, 2418] } ), @@ -57,9 +56,7 @@ suite.addBatch(test.batch({ }, { 3e5: [0, 136, 3185, 21879, 54600, 115200, 209550, 345150, 529500, 770100, 1074450, 0, 0], 36e5: [3321, 3120429], - 864e5: [3321, 3120429], - 6048e5: [3123750], - 2592e6: [3123750] + 864e5: [3321, 3120429] } ), @@ -70,9 +67,7 @@ suite.addBatch(test.batch({ }, { 3e5: [0, 1.36, 31.85, 218.79, 546, 1152, 2095.5, 3451.5, 5295, 7701, 10744.5, 0, 0], 36e5: [33.21, 31204.29], - 864e5: [33.21, 31204.29], - 6048e5: [31237.5], - 2592e6: [31237.5] + 864e5: [33.21, 31204.29] } ), @@ -83,9 +78,7 @@ suite.addBatch(test.batch({ }, { 3e5: [NaN, 16, 64, 142, 174, 224, 274, 324, 374, 424, 474, NaN, NaN], 36e5: [81, 2417], - 864e5: [81, 2417], - 6048e5: [2499], - 2592e6: [2499] + 864e5: [81, 2417] } ), @@ -96,9 +89,7 @@ suite.addBatch(test.batch({ }, { 3e5: [0, 17, 65, 143, 175, 225, 275, 325, 375, 425, 475, 0, 0], 36e5: [82, 2418], - 864e5: [82, 2418], - 6048e5: [2500], - 2592e6: [2500] + 864e5: [82, 2418] } ), @@ -109,9 +100,7 @@ suite.addBatch(test.batch({ }, { 3e5: [NaN, 128, 3136, 21726, 54288, 114688, 208788, 344088, 528088, 768288, 1072188, NaN, NaN], 36e5: [3280.5, 3119138.5], - 864e5: [3280.5, 3119138.5], - 6048e5: [3122500.5], - 2592e6: [3122500.5] + 864e5: [3280.5, 3119138.5] } ), @@ -122,9 +111,7 @@ suite.addBatch(test.batch({ }, { 3e5: [-1, 16, 64, 142, 174, 224, 274, 324, 374, 424, 474, -1, -1], 36e5: [81, 2417], - 864e5: [81, 2417], - 6048e5: [2499], - 2592e6: [2499] + 864e5: [81, 2417] } ) })); @@ -201,6 +188,8 @@ function metricTest(request, expected) { assert.deepEqual(actual.map(function(d) { return d.time; }), times); // returns the expected values + var actualValues = actual.map(function(d) { return d.value; }); + assert.equal(expected.length, actual.length, "expected " + expected + ", got " + actualValues); expected.forEach(function(value, i) { if (Math.abs(actual[i].value - value) > 1e-6) { assert.fail(actual.map(function(d) { return d.value; }), expected, "expected {expected}, got {actual} at " + actual[i].time.toISOString()); diff --git a/test/reduces-test.js b/test/reduces-test.js index 3eb3d8b0..3fa0c62b 100644 --- a/test/reduces-test.js +++ b/test/reduces-test.js @@ -1,6 +1,6 @@ var vows = require("vows"), assert = require("assert"), - reduces = require("../lib/cube/server/reduces"); + reduces = require("../lib/cube/reduces"); var suite = vows.describe("reduces"); diff --git a/test/tiers-test.js b/test/tiers-test.js index baec39f5..a638b260 100644 --- a/test/tiers-test.js +++ b/test/tiers-test.js @@ -1,6 +1,6 @@ var vows = require("vows"), assert = require("assert"), - tiers = require("../lib/cube/server/tiers"); + tiers = require("../lib/cube/tiers"); var suite = vows.describe("tiers"); @@ -13,7 +13,7 @@ suite.addBatch({ keys.push(+key); } keys.sort(function(a, b) { return a - b; }); - assert.deepEqual(keys, [1e4, 3e5, 36e5, 864e5, 6048e5, 2592e6]); + assert.deepEqual(keys, [1e4, 6e4, 3e5, 36e5, 864e5]); } }, @@ -79,6 +79,71 @@ suite.addBatch({ } } }, + + "minute": { + topic: tiers[6e4], + "has the key 6e4": function(tier) { + assert.strictEqual(tier.key, 6e4); + }, + "next is undefined": function(tier) { + assert.isUndefined(tier.next); + }, + "size is undefined": function(tier) { + assert.isUndefined(tier.size); + }, + + "floor": { + "rounds down to minutes": function(tier) { + assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 20, 00)), utc(2011, 08, 02, 12, 20)); + assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 20, 01)), utc(2011, 08, 02, 12, 20)); + assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 21, 00)), utc(2011, 08, 02, 12, 21)); + assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 23, 00)), utc(2011, 08, 02, 12, 23)); + assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 24, 59)), utc(2011, 08, 02, 12, 24)); + assert.deepEqual(tier.floor(utc(2011, 08, 02, 12, 25, 00)), utc(2011, 08, 02, 12, 25)); + }, + "does not modify the passed-in date": function(tier) { + var date = utc(2011, 08, 02, 12, 21, 20); + assert.deepEqual(tier.floor(date), utc(2011, 08, 02, 12, 21)); + assert.deepEqual(date, utc(2011, 08, 02, 12, 21, 20)); + } + }, + + "ceil": { + "rounds up to minutes": function(tier) { + assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 20, 00)), utc(2011, 08, 02, 12, 20)); + assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 20, 01)), utc(2011, 08, 02, 12, 21)); + assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 21, 00)), utc(2011, 08, 02, 12, 21)); + assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 23, 00)), utc(2011, 08, 02, 12, 23)); + assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 24, 59)), utc(2011, 08, 02, 12, 25)); + assert.deepEqual(tier.ceil(utc(2011, 08, 02, 12, 25, 00)), utc(2011, 08, 02, 12, 25)); + }, + "does not modified the specified date": function(tier) { + var date = utc(2011, 08, 02, 12, 21, 20); + assert.deepEqual(tier.ceil(date), utc(2011, 08, 02, 12, 22)); + assert.deepEqual(date, utc(2011, 08, 02, 12, 21, 20)); + } + }, + + "step": { + "increments time by one minute": function(tier) { + var date = utc(2011, 08, 02, 23, 45, 00); + assert.deepEqual(date = tier.step(date), utc(2011, 08, 02, 23, 46)); + assert.deepEqual(date = tier.step(date), utc(2011, 08, 02, 23, 47)); + assert.deepEqual(date = tier.step(date), utc(2011, 08, 02, 23, 48)); + assert.deepEqual(date = tier.step(date), utc(2011, 08, 02, 23, 49)); + assert.deepEqual(date = tier.step(date), utc(2011, 08, 02, 23, 50)); + }, + "does not round the specified date": function(tier) { + assert.deepEqual(tier.step(utc(2011, 08, 02, 12, 21, 23)), utc(2011, 08, 02, 12, 22, 23)); + }, + "does not modify the specified date": function(tier) { + var date = utc(2011, 08, 02, 12, 20); + assert.deepEqual(tier.step(date), utc(2011, 08, 02, 12, 21)); + assert.deepEqual(date, utc(2011, 08, 02, 12, 20)); + } + } + }, + "minute5": { topic: tiers[3e5], "has the key 3e5": function(tier) { @@ -265,132 +330,6 @@ suite.addBatch({ assert.deepEqual(date, utc(2011, 08, 02, 00, 00)); } } - }, - - "week": { - topic: tiers[6048e5], - "has the key 6048e5": function(tier) { - assert.strictEqual(tier.key, 6048e5); - }, - "next is the one-day tier": function(tier) { - assert.equal(tier.next, tiers[864e5]); - }, - "size is 7": function(tier) { - assert.strictEqual(tier.size(), 7); - }, - - "floor": { - "rounds down to weeks": function(tier) { - assert.deepEqual(tier.floor(utc(2011, 08, 04, 00, 00, 00)), utc(2011, 08, 04, 00, 00)); - assert.deepEqual(tier.floor(utc(2011, 08, 04, 00, 00, 01)), utc(2011, 08, 04, 00, 00)); - assert.deepEqual(tier.floor(utc(2011, 08, 04, 12, 21, 00)), utc(2011, 08, 04, 00, 00)); - assert.deepEqual(tier.floor(utc(2011, 08, 10, 23, 59, 59)), utc(2011, 08, 04, 00, 00)); - assert.deepEqual(tier.floor(utc(2011, 08, 11, 00, 00, 00)), utc(2011, 08, 11, 00, 00)); - }, - "does not modify the passed-in date": function(tier) { - var date = utc(2011, 08, 04, 12, 21); - assert.deepEqual(tier.floor(date), utc(2011, 08, 04, 00, 00)); - assert.deepEqual(date, utc(2011, 08, 04, 12, 21)); - } - }, - - "ceil": { - "rounds up to weeks": function(tier) { - assert.deepEqual(tier.ceil(utc(2011, 08, 04, 00, 00, 00)), utc(2011, 08, 04, 00, 00)); - assert.deepEqual(tier.ceil(utc(2011, 08, 04, 00, 00, 01)), utc(2011, 08, 11, 00, 00)); - assert.deepEqual(tier.ceil(utc(2011, 08, 04, 12, 21, 00)), utc(2011, 08, 11, 00, 00)); - assert.deepEqual(tier.ceil(utc(2011, 08, 10, 23, 59, 59)), utc(2011, 08, 11, 00, 00)); - assert.deepEqual(tier.ceil(utc(2011, 08, 11, 00, 00, 00)), utc(2011, 08, 11, 00, 00)); - }, - "does not modified the specified date": function(tier) { - var date = utc(2011, 08, 02, 12, 21, 00); - assert.deepEqual(tier.ceil(date), utc(2011, 08, 04, 00, 00)); - assert.deepEqual(date, utc(2011, 08, 02, 12, 21)); - } - }, - - "step": { - "increments time by one week": function(tier) { - var date = utc(2011, 08, 04, 00, 00, 00); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 11, 00, 00)); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 18, 00, 00)); - assert.deepEqual(date = tier.step(date), utc(2011, 08, 25, 00, 00)); - assert.deepEqual(date = tier.step(date), utc(2011, 09, 02, 00, 00)); - assert.deepEqual(date = tier.step(date), utc(2011, 09, 09, 00, 00)); - }, - "does not round the specified date": function(tier) { - assert.deepEqual(tier.step(utc(2011, 08, 02, 12, 21, 23)), utc(2011, 08, 09, 12, 21, 23)); - }, - "does not modify the specified date": function(tier) { - var date = utc(2011, 08, 04, 00, 00, 00); - assert.deepEqual(tier.step(date), utc(2011, 08, 11, 00, 00)); - assert.deepEqual(date, utc(2011, 08, 04, 00, 00)); - } - } - }, - - "month": { - topic: tiers[2592e6], - "has the key 2592e6": function(tier) { - assert.strictEqual(tier.key, 2592e6); - }, - "next is the one-day tier": function(tier) { - assert.equal(tier.next, tiers[864e5]); - }, - "size is number of days in a month": function(tier) { - assert.strictEqual(tier.size(utc(2011, 00, 01)), 31); - assert.strictEqual(tier.size(utc(2011, 01, 01)), 28); - }, - - "floor": { - "rounds down to months": function(tier) { - assert.deepEqual(tier.floor(utc(2011, 08, 01, 00, 00, 00)), utc(2011, 08, 01, 00, 00)); - assert.deepEqual(tier.floor(utc(2011, 08, 01, 00, 00, 01)), utc(2011, 08, 01, 00, 00)); - assert.deepEqual(tier.floor(utc(2011, 08, 04, 12, 21, 00)), utc(2011, 08, 01, 00, 00)); - assert.deepEqual(tier.floor(utc(2011, 08, 29, 23, 59, 59)), utc(2011, 08, 01, 00, 00)); - assert.deepEqual(tier.floor(utc(2011, 09, 01, 00, 00, 00)), utc(2011, 09, 01, 00, 00)); - }, - "does not modify the passed-in date": function(tier) { - var date = utc(2011, 08, 04, 12, 21); - assert.deepEqual(tier.floor(date), utc(2011, 08, 01, 00, 00)); - assert.deepEqual(date, utc(2011, 08, 04, 12, 21)); - } - }, - - "ceil": { - "rounds up to weeks": function(tier) { - assert.deepEqual(tier.ceil(utc(2011, 08, 01, 00, 00, 00)), utc(2011, 08, 01, 00, 00)); - assert.deepEqual(tier.ceil(utc(2011, 08, 01, 00, 00, 01)), utc(2011, 09, 01, 00, 00)); - assert.deepEqual(tier.ceil(utc(2011, 08, 04, 12, 21, 00)), utc(2011, 09, 01, 00, 00)); - assert.deepEqual(tier.ceil(utc(2011, 08, 29, 23, 59, 59)), utc(2011, 09, 01, 00, 00)); - assert.deepEqual(tier.ceil(utc(2011, 09, 01, 00, 00, 00)), utc(2011, 09, 01, 00, 00)); - }, - "does not modified the specified date": function(tier) { - var date = utc(2011, 08, 02, 12, 21, 00); - assert.deepEqual(tier.ceil(date), utc(2011, 09, 01, 00, 00)); - assert.deepEqual(date, utc(2011, 08, 02, 12, 21)); - } - }, - - "step": { - "increments time by one month": function(tier) { - var date = utc(2011, 08, 01, 00, 00, 00); - assert.deepEqual(date = tier.step(date), utc(2011, 09, 01, 00, 00)); - assert.deepEqual(date = tier.step(date), utc(2011, 10, 01, 00, 00)); - assert.deepEqual(date = tier.step(date), utc(2011, 11, 01, 00, 00)); - assert.deepEqual(date = tier.step(date), utc(2012, 00, 01, 00, 00)); - assert.deepEqual(date = tier.step(date), utc(2012, 01, 01, 00, 00)); - }, - "does not round the specified date": function(tier) { - assert.deepEqual(tier.step(utc(2011, 01, 02, 12, 21, 23)), utc(2011, 02, 02, 12, 21, 23)); - assert.deepEqual(tier.step(utc(2011, 08, 02, 12, 21, 23)), utc(2011, 09, 02, 12, 21, 23)); - }, - "does not modify the specified date": function(tier) { - var date = utc(2011, 08, 01, 00, 00, 00); - assert.deepEqual(tier.step(date), utc(2011, 09, 01, 00, 00)); - assert.deepEqual(date, utc(2011, 08, 01, 00, 00)); - } - } } }); diff --git a/test/types-test.js b/test/types-test.js index cddd6c9b..7e211c96 100644 --- a/test/types-test.js +++ b/test/types-test.js @@ -1,7 +1,7 @@ var vows = require("vows"), assert = require("assert"), test = require("./test"), - types = require("../lib/cube/server/types"), + types = require("../lib/cube/types"), mongodb = require("mongodb"); var suite = vows.describe("types");