diff --git a/fnordmetric-webui/.gitignore b/fnordmetric-webui/.gitignore
index 141bce8de..98c551dbd 100644
--- a/fnordmetric-webui/.gitignore
+++ b/fnordmetric-webui/.gitignore
@@ -1 +1,2 @@
fnordmetric-webui.js
+tests/
diff --git a/fnordmetric-webui/fnordmetric-webui-datepicker.js b/fnordmetric-webui/fnordmetric-webui-datepicker.js
index ab49f6ddc..012ce9721 100644
--- a/fnordmetric-webui/fnordmetric-webui-datepicker.js
+++ b/fnordmetric-webui/fnordmetric-webui-datepicker.js
@@ -16,43 +16,155 @@ if (FnordMetric.views === undefined) {
FnordMetric.views = {};
}
+ /* generate html for time input and handle input */
+FnordMetric.util.timeInput = function(selectedTimestamp, elem, callback) {
+ var selectedMinutes =
+ FnordMetric.util.appendLeadingZero(
+ selectedTimestamp.getMinutes());
+
+ var selectedHours =
+ FnordMetric.util.appendLeadingZero(
+ selectedTimestamp.getHours());
+
+ var input_container = document.createElement("div");
+ input_container.className = "input_container";
+ var separator = document.createElement("span");
+ separator.innerHTML = ":";
+
+ var hour_input = document.createElement("input");
+ hour_input.placeholder = selectedHours;
+
+ var minute_input = document.createElement("input");
+ minute_input.placeholder = selectedMinutes;
+
+ function render() {
+ input_container.appendChild(hour_input);
+ input_container.appendChild(separator);
+ input_container.appendChild(minute_input);
+ elem.appendChild(input_container);
+
+ hour_input.addEventListener('focus', function(e) {
+ e.preventDefault();
+ FnordMetric.util.validatedTimeInput(this, "hour", callback);
+ }, false);
+
+ minute_input.addEventListener('focus', function(e) {
+ e.preventDefault();
+ FnordMetric.util.validatedTimeInput(this, "minute", callback);
+ }, false);
+
+ }
+
+ function getValues() {
+ var hours = hour_input.value.length > 0 ?
+ hour_input.value : selectedHours;
+ var minutes = minute_input.value.length > 0 ?
+ minute_input.value : selectedMinutes;
+ return {
+ "hours" : hours,
+ "minutes" : minutes
+ }
+ }
+
+ return {
+ "render" : render,
+ "getValues" : getValues,
+ }
+}
+
+
+/* Displays a calendar from an input for selecting time and date*/
FnordMetric.util.DatePicker = function(elem, dp_input, viewport, callback) {
+ /* current date */
+ var currMonth;
+ var currYear;
+ var currDate;
+
+ /* in datepicker selected date */
+ var selectedTimestamp;
+ var selectedYear;
+ var selectedMonth;
+ var selectedDate;
+
+ var timeInput;
var dp_widget = document.createElement("div");
dp_widget.className = "datepicker_widget";
elem.appendChild(dp_widget);
- var curr_day = new Date().getDay();
- var curr_date = new Date().getDate();
- var curr_month = new Date().getMonth();
- var curr_year = new Date().getFullYear();
- var m_month;
- var y_year;
- var isCurrMonth;
- var selected_ts;
- var selected_hours;
- var selected_minutes;
+ function getDaysInMonth(year, month) {
+ return (new Date(year, (month+1), 0).getDate());
+ }
- var human_days = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
+ function getFirstDayOfMonth(year, month) {
+ return (new Date(year + "-" + (month+1) + "-01").getDay());
+ }
+ /*checks if the day is in this month or not
+ day and first_day are numeric weekday description
+ rowNum the actual row and date the actual date
+ */
+ function inThisMonth(day, first_day, date, daysInMonth, rowNum) {
+ if (rowNum > 0 && rowNum < 4) {return true}
+ if (rowNum == 0) {
+ /* the calendar has the same number of rows for each month */
+ if (first_day == 0) {
+ return false;
+ } else if (day < first_day) {
+ return false;
+ } else {
+ return true;
+ }
+ } else if (rowNum >= 4) {
+ if (date <= daysInMonth) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
- function resetDatepicker() {
- dp_widget.innerHTML = "";
- dp_widget.className = "datepicker_widget";
+ function isSelectable(date, month, year) {
+ if (month == currMonth) {
+ return date <= currDate;
+ }
+ return (year <= currYear && month < currMonth);
}
+ /* if date is selectable */
+ function getCellName(date, month, year) {
+ var name = "";
+ /* date is today */
+ if (date == currDate && month == currMonth && year == currYear) {
+ name = "highlight_border";
+ }
+ /* date is selected date */
+ if (date == selectedDate &&
+ month == selectedMonth &&
+ year == selectedYear) {
+ name += " highlight";
+ }
+ return name;
+ }
- function onSelect(hours, minutes, day, month, year) {
- var hours = (hours.length > 0)? hours : selected_hours;
- var minutes = (minutes.length > 0)? minutes : selected_minutes;
- var ts = new Date(year, month, day, hours, minutes).getTime();
+ function onSelect(date, month, year) {
+ /* fallback for time inputs */
+ var date = (date == undefined) ?
+ selectedDate : date;
+ var month = (month == undefined)?
+ selectedMonth : month;
+ var year = (year == undefined) ?
+ selectedYear : year;
+ var inputs = timeInput.getValues();
+ var hours = inputs.hours;
+ var minutes = inputs.minutes;
+ var ts = new Date(year, month, date, hours, minutes).getTime();
if (ts <= Date.now() ) {
-
dp_input.value =
FnordMetric.util.appendLeadingZero(month+1) +
"/" +
- FnordMetric.util.appendLeadingZero(day) +
+ FnordMetric.util.appendLeadingZero(date) +
"/" + year + " " +
FnordMetric.util.appendLeadingZero(hours) +
":" +
@@ -64,227 +176,145 @@ FnordMetric.util.DatePicker = function(elem, dp_input, viewport, callback) {
}
}
- function isSelectable(day) {
- if (isCurrMonth) {
- return day <= curr_date;
- }
- return y_year <= curr_year && m_month <= curr_month;
- }
-
-
- function init(month, year) {
- y_year = year;
- m_month = month;
-
- dp_widget.className = "datepicker_widget active";
- selected_ts = new Date(
- parseInt(dp_input.getAttribute("id"), 10));
-
- selected_hours =
- FnordMetric.util.appendLeadingZero(
- selected_ts.getHours());
-
- selected_minutes =
- FnordMetric.util.appendLeadingZero(
- selected_ts.getMinutes());
-
- var sltd_date = selected_ts.getDate();
- var sltd_month = selected_ts.getMonth();
- var sltd_year = selected_ts.getYear();
- var isSltdMonth = (month == sltd_month && year == curr_year);
- isCurrMonth = (month == curr_month && year == curr_year);
-
- var input_container = document.createElement("div");
- input_container.className = "input_container";
- var separator = document.createElement("span");
- separator.innerHTML = ":";
-
- var hour_input = document.createElement("input");
-
- hour_input.placeholder = selected_hours;
- hour_input.addEventListener('focus', function(e) {
+ /* sets event listener for selectable dates */
+ function handleDateLinks(link, date, month, year) {
+ link.addEventListener('click', function(e) {
e.preventDefault();
- timeInputFocus = true;
- FnordMetric.util.validatedTimeInput(this, "hour");
+ onSelect(date, month, year);
}, false);
+ }
- var minute_input = document.createElement("input");
- minute_input.placeholder = selected_minutes;
- minute_input.addEventListener('focus', function() {
- timeInputFocus = true;
- FnordMetric.util.validatedTimeInput(this, "minute");
- }, false);
+ function resetDatepicker() {
+ dp_widget.innerHTML = "";
+ dp_widget.className = "datepicker_widget";
+ }
+ function resetCalendar(table) {
+ table.innerHTML = "";
+ FnordMetric.util.removeIfChild(
+ table, dp_widget);
+ }
- input_container.appendChild(hour_input);
- input_container.appendChild(separator);
- input_container.appendChild(minute_input);
- dp_widget.appendChild(input_container);
+ function renderWeekHeader(table) {
+ var header = document.createElement("tr");
+ var dayNames =
+ ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
- var first_day = new Date(year + "-" + (month+1) + "-01").getDay();
- /* Mo = 1, ... , Su = 7 */
- first_day = (first_day === 0)? 7 : first_day-1;
- var num_days = new Date(year, (month+1), 0).getDate();
- var table = document.createElement("table");
+ dayNames.map(function(day) {
+ var cell = document.createElement("th");
+ cell.innerHTML = day;
+ header.appendChild(cell);
+ });
+
+ table.appendChild(header);
+ }
- var month_header = document.createElement("tr");
+ function renderMonthHeader(year, month, table) {
+ var year = parseInt(year, 10);
+ var month = parseInt(month, 10);
+ var row = document.createElement("tr");
+ var cell = document.createElement("td");
+ cell.colSpan = "7";
+ var month_title = document.createElement("span");
+ month_title.className = "datepicker_title";
+ month_title.innerHTML =
+ FnordMetric.util.getMonthStr(month) + " " + year;
+
+ /* tooltip to select previous month */
var prev_ttp = FnordMetric.createButton(
"#", "month_ttp", "");
+
+ /* tooltip to select next month */
+ var next_ttp = FnordMetric.createButton(
+ "#", "month_ttp", "");
+
prev_ttp.addEventListener('click', function(e) {
e.preventDefault();
- resetDatepicker();
+ resetCalendar(table);
year = (month == 0)? year-1 : year;
- init((month-1 +12) % 12, year);
+ month = (month + 11) % 12;
+ renderCalendar(year, month);
}, false);
- var next_ttp = FnordMetric.createButton(
- "#", "month_ttp", "");
next_ttp.addEventListener('click', function(e) {
e.preventDefault();
- resetDatepicker();
+ resetCalendar(table);
year = (month == 11)? year+1 : year;
- init((month + 1) % 12, year);
+ month = (month + 1) % 12;
+ renderCalendar(year, month);
}, false);
- var month_title = document.createElement("span");
- month_title.innerHTML =
- FnordMetric.util.getMonthStr(month) + " " + year;
- month_title.className = "datepicker_title";
- var month_cell = document.createElement("td");
- month_cell.colSpan = "7";
- month_cell.appendChild(prev_ttp);
- month_cell.appendChild(month_title);
- month_cell.appendChild(next_ttp);
- month_header.appendChild(month_cell);
-
-
- var day_header = document.createElement("tr");
- human_days.map(function(day) {
- var header_cell = document.createElement("th");
- header_cell.innerHTML = day;
- day_header.appendChild(header_cell);
- });
- var is
- var day = 1;
- var rows = 0;
- var first_row = document.createElement("tr");
- rows++;
- for (var i = 0; i < 7; i++) {
- var cell = document.createElement("td");
- if (i < first_day || first_day == 0) {
- cell.innerHTML = "";
- } else {
-
- if (isSelectable(day)) {
- if (isCurrMonth && day == curr_date) {
- cell.className = "highlight_border";
- }
- if (isSltdMonth && day == sltd_date) {
- cell.className += " highlight";
- }
-
- var link = FnordMetric.createButton(
- "#", undefined, day);
- link.addEventListener('click', function(e) {
- e.preventDefault();
- onSelect(
- hour_input.value,
- minute_input.value,
- this.innerText, month, year);
- }, false);
-
- cell.appendChild(link);
- } else {
- cell.innerHTML = day;
- }
+ cell.appendChild(prev_ttp);
+ cell.appendChild(month_title);
+ cell.appendChild(next_ttp);
+ row.appendChild(cell);
+ table.appendChild(row);
+ }
- day++;
- }
- first_row.appendChild(cell);
- }
- table.appendChild(day_header);
- table.appendChild(month_header);
- table.appendChild(first_row);
+ function renderWeeks(year, month, calendar) {
+ var daysInMonth = getDaysInMonth(year, month);
+ var fDay = getFirstDayOfMonth(year, month);
+ var numRows = 6;
+ var date = 1;
- while (rows < 6 && day <= num_days) {
+ /* generate the rows (that is the weeks) */
+ for (var dprow = 0; dprow < numRows; dprow++) {
var row = document.createElement("tr");
- rows++;
- for (var i = 0; i < 7 && day <= num_days; i++) {
+ for (day = 0; day < 7; day ++) {
var cell = document.createElement("td");
-
- if (isSelectable(day)) {
- if (isCurrMonth && day == curr_date) {
- cell.className = "highlight_border";
- }
- if (isSltdMonth && day == sltd_date) {
- cell.className += " highlight";
+ if (inThisMonth(day, fDay, date, daysInMonth, dprow)) {
+ if (isSelectable(date, month, year)) {
+ var link = FnordMetric.createButton(
+ "#", undefined, date);
+ cell.className = getCellName(date, month, year);
+ cell.appendChild(link);
+ handleDateLinks(link, date, month, year);
+ } else {
+ cell.innerHTML = date;
}
-
- var link = FnordMetric.createButton(
- "#", undefined, day);
- link.addEventListener('click', function(e) {
- e.preventDefault();
- onSelect(
- hour_input.value,
- minute_input.value,
- this.innerText, month, year);
- }, false);
-
- cell.appendChild(link);
- } else {
- cell.innerHTML = day;
+ date++;
}
-
row.appendChild(cell);
- day++;
}
- table.appendChild(row);
+ calendar.appendChild(row);
}
+ }
- if (rows < 6) {
- var last_row = document.createElement("tr");
- for (var i = 0; i < 7; i++) {
- var cell = document.createElement("td");
- if (day <= num_days) {
-
- if (isSelectable(day)) {
- if (isCurrMonth && day == curr_date) {
- cell.className = "highlight_border";
- }
- if (isSltdMonth && day == sltd_date) {
- cell.className += " highlight";
- }
- var link = FnordMetric.createButton(
- "#", undefined, day);
- link.addEventListener('click', function(e) {
- e.preventDefault();
- onSelect(
- hour_input.value,
- minute_input.value,
- this.innerText, month, year);
- }, false);
+ function renderCalendar(year, month) {
+ var table = document.createElement("table");
+ renderWeekHeader(table);
+ renderMonthHeader(year, month, table);
+ renderWeeks(year, month, table);
+ dp_widget.appendChild(table);
+ }
- cell.appendChild(link);
- } else {
- cell.innerHTML = day;
- }
+ function init() {
+ var now = new Date();
+ currYear = now.getFullYear();
+ currMonth = now.getMonth();
+ currDate = now.getDate();
- day++;
- } else {
- cell.innerHTML = "";
- }
- last_row.appendChild(cell);
- }
- table.appendChild(last_row);
- }
+ selectedTimestamp = new Date(
+ parseInt(dp_input.getAttribute("id"), 10));
+ selectedYear = selectedTimestamp.getFullYear();
+ selectedMonth = selectedTimestamp.getMonth();
+ selectedDate = selectedTimestamp.getDate();
- dp_widget.appendChild(table);
+ dp_widget.innerHTML = "";
+ dp_widget.className += " active";
+ timeInput = FnordMetric.util.timeInput(
+ selectedTimestamp, dp_widget, onSelect);
+ timeInput.render();
+ renderCalendar(currYear, currMonth);
}
+ dp_input.addEventListener('focus', function(event) {
+ init();
+ }, false);
document.addEventListener('click', function() {
resetDatepicker();
@@ -294,14 +324,5 @@ FnordMetric.util.DatePicker = function(elem, dp_input, viewport, callback) {
elem.addEventListener('click', function(e) {
e.stopPropagation();
});
-
- dp_input.addEventListener('focus', function(event) {
- dp_widget.innerHTML = "";
- var now = new Date();
- var month = now.getMonth();
- var year = now.getFullYear();
- init(month, year)
- }, false);
-
-};
+}
diff --git a/fnordmetric-webui/fnordmetric-webui-metriclist.js b/fnordmetric-webui/fnordmetric-webui-metriclist.js
index 7920bc16d..0b3e6e9f9 100644
--- a/fnordmetric-webui/fnordmetric-webui-metriclist.js
+++ b/fnordmetric-webui/fnordmetric-webui-metriclist.js
@@ -79,8 +79,7 @@ FnordMetric.views.MetricList = function() {
for (i in metrics) {
table_view.addRow([
metrics[i]["key"],
- FnordMetric.util.convertArrayToString(
- metrics[i]["labels"]),
+ metrics[i]["labels"].join(", "),
FnordMetric.util.parseTimestamp(
metrics[i]["last_insert"]),
metrics[i]["total_bytes"]]);
diff --git a/fnordmetric-webui/fnordmetric-webui-metricpreviewwidget.js b/fnordmetric-webui/fnordmetric-webui-metricpreviewwidget.js
index 5a47c89b5..24e9965a9 100644
--- a/fnordmetric-webui/fnordmetric-webui-metricpreviewwidget.js
+++ b/fnordmetric-webui/fnordmetric-webui-metricpreviewwidget.js
@@ -56,12 +56,16 @@ FnordMetric.util.MetricPreviewWidget = function(viewport, query_params) {
/* checks if required url params are misssing and adds those if so */
function addRequiredURLParamsForView(value) {
- if (value == "count" || value == "sum" || value == "mean") {
-
- var time_step = query_params.t_step;
- if (time_step == undefined) {
- time_step = defaults.t_step;
- updateURLParams("t_step", time_step);
+ if (value == "count" ||
+ value == "sum" ||
+ value == "mean" ||
+ value == "min" ||
+ value == "max") {
+
+ var time_window = query_params.t_window;
+ if (time_window == undefined) {
+ time_window = defaults.t_window;
+ updateURLParams("t_window", time_window);
}
}
var group_by = query_params.by;
@@ -479,10 +483,10 @@ FnordMetric.util.MetricPreviewWidget = function(viewport, query_params) {
prev_timespan.addEventListener('click', function(e) {
e.preventDefault();
- var end = end_time;
- end_time = start_time;
- var diff = end - start_time;
- start_time = start_time - diff;
+ var end = query_params.end_time;
+ end_time = query_params.start_time;
+ var diff = end - query_params.start_time;
+ start_time = query_params.start_time - diff;
updateURLParams("end_time", end_time);
updateURLParams("start_time", start_time);
updateDateTimeElems(
@@ -492,11 +496,13 @@ FnordMetric.util.MetricPreviewWidget = function(viewport, query_params) {
next_timespan.addEventListener('click', function(e) {
e.preventDefault();
- var start = start_time;
- var end = parseInt(end_time ,10) + (end_time - start);
+ var start = query_params.start_time;
+ var end =
+ parseInt(query_params.end_time ,10) +
+ (query_params.end_time - start);
if (end <= now) {
- start_time = end_time;
+ start_time = query_params.end_time;
end_time = end;
updateURLParams("start_time", start_time);
updateURLParams("end_time", end_time);
diff --git a/fnordmetric-webui/fnordmetric-webui-queryeditor.js b/fnordmetric-webui/fnordmetric-webui-queryeditor.js
index dd83ece0a..a5c93ed03 100644
--- a/fnordmetric-webui/fnordmetric-webui-queryeditor.js
+++ b/fnordmetric-webui/fnordmetric-webui-queryeditor.js
@@ -126,6 +126,12 @@ FnordMetric.views.QueryPlayground = function() {
}
}
+ function beforeUnloadEvent(e) {
+ e.returnValue =
+ "You may loose your query when leaving the page.";
+ }
+
+
function render(viewport, url, query_params) {
direction = "horizontal";
viewport = viewport;
@@ -169,13 +175,10 @@ FnordMetric.views.QueryPlayground = function() {
/* set eventListeners */
- window.onbeforeunload = function(e) {
- return "You may loose your query when leaving the page.";
- }
+ window.addEventListener(
+ 'beforeunload', beforeUnloadEvent, false);
- window.addEventListener('resize', function() {
- updateLayout()
- });
+ window.addEventListener('resize', updateLayout);
editor_pane.addEventListener('keydown', function(e) {
if ((e.ctrlKey || e.metaKey) && e.keyCode == 13) {
@@ -268,7 +271,8 @@ FnordMetric.views.QueryPlayground = function() {
}
function destroy() {
-
+ window.removeEventListener('beforeunload', beforeUnloadEvent, false);
+ window.removeEventListener('resize', updateLayout);
}
return {
diff --git a/fnordmetric-webui/fnordmetric-webui-unittests.js b/fnordmetric-webui/fnordmetric-webui-unittests.js
new file mode 100644
index 000000000..b25b3f9c3
--- /dev/null
+++ b/fnordmetric-webui/fnordmetric-webui-unittests.js
@@ -0,0 +1,221 @@
+/**
+ * This file is part of the "FnordMetric" project
+ * Copyright (c) 2014 Laura Schlimmer
+ * Copyright (c) 2014 Paul Asmuth, Google Inc.
+ *
+ * FnordMetric is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License v3.0. You should have received a
+ * copy of the GNU General Public License along with this program. If not, see
+ * .
+ */
+if (FnordMetric === undefined) {
+ FnordMetric = {};
+}
+
+if (FnordMetric.util === undefined) {
+ FnordMetric.util = {};
+}
+
+/* Unit Tests for FnordMetric.util functions */
+FnordMetric.UnitTests = function() {
+ console.log("make unit tests");
+ var results = {
+ total : 0,
+ bad : 0
+ }
+
+ var testParseQueryString = function() {
+ function test(querystr, expected) {
+ results.total++;
+ var result = FnordMetric.util.parseQueryString(querystr);
+ result = JSON.stringify(result);
+ expected = JSON.stringify(expected);
+ if (result != expected) {
+ results.bad++;
+ console.log("Testing parseQueryString expected "
+ + expected + ", but was " + result);
+ }
+ }
+ test("metric_list?metric=/osx/load_avg_15m&view=value",
+ {path : "metric_list",
+ query_params : {
+ innerView : "metric",
+ innerViewValue : "/osx/load_avg_15m",
+ view : "value"}});
+
+ test("metric_list", {path : "metric_list", query_params : {}});
+ test("query_playground?sql_query=select%20*%20from%20http_status_codes%3B",
+ {path : "query_playground",
+ query_params : {
+ innerView : "sql_query",
+ innerViewValue : decodeURIComponent("select%20*%20from%20http_status_codes%3B")}});
+ test("", {path : "", query_params: {}});
+ test("undefined", {path: "", query_params:{}});
+ }();
+
+ var testSetUrlQueryString = function() {
+ function test(hash, query_params, encode, push_state, expected) {
+ results.total++;
+ FnordMetric.util.setURLQueryString(hash, query_params, encode, push_state);
+ var result = window.location.hash;
+ if (result != expected) {
+ results.bad++;
+ console.log("Testing setURLQueryString expected "
+ + expected + ", but was " + result);
+ }
+ }
+ test("metric_list", {}, false, false, "#metric_list");
+ test(undefined,
+ {innerView : "metric",
+ innerViewValue : "http_status_codes"},
+ false,
+ false,
+ "");
+
+ test("metric_list",
+ {innerView : undefined,
+ innerViewValue : "http_status_codes"},
+ false,
+ false,
+ "#metric_list");
+
+ test("query_playground",
+ {innerView : "sql_query",
+ innerViewValue : "SELECT * FROM `http_status_codes`;"},
+ true,
+ true,
+ "#query_playground?sql_query=SELECT%20*%20FROM%20%60http_status_codes%60%3B");
+ test("", {}, false, false, "");
+ }();
+
+ var testParseTimestamp = function() {
+ function test(timestamp, expected) {
+ results.total++;
+ var result = FnordMetric.util.parseTimestamp(timestamp);
+ if (result.substr(0,13) != expected) {
+ results.bad++;
+ console.log("Testing parseTimestamp expected " +
+ expected + ", but was " + result.substr(0,12));
+ }
+ }
+ var now = Date.now();
+ test(now, "0 seconds ago");
+ test(now *1000, "0 seconds ago")
+ test(Math.floor(now / 1000), "0 seconds ago");
+ }();
+
+ var testSortMetricList = function() {
+ function test(metrics, column_index, order, expected) {
+ results.total++;
+ FnordMetric.util.sortMetricList(metrics, column_index, order);
+ var result = metrics;
+ if (result[0][column_index] != expected[0][column_index]) {
+ results.bad++;
+ console.log("Testing sortMetricList expected "
+ + expected[0][column_index] + ", but was " + result[0][column_index]);
+ }
+ }
+ var metric1 = [
+ "/osx/load_avg_15m", "host", "5 hours ago - Nov 18 2014 11:33:11", 10693668];
+ var metric2 = [
+ "/osx/load_avg_5m", "", "5 hours ago - Nov 18 2014 11:33:12", 10693669];
+
+ test([metric1, metric2], 0, "asc", [metric1, metric2]);
+ test([metric1, metric2], 0, "desc", [metric2, metric1]);
+ test([metric1, metric1], 0, "asc", [metric1, metric1]);
+ test([metric1, metric2], 2, "desc", [metric2, metric1]);
+ test([metric1, metric2], 2, "asc", [metric1, metric2]);
+ test([metric1, metric2], 1, "desc", [metric1, metric2]);
+
+ }();
+
+ var testSearchMetricList = function() {
+ function test(metrics, search_item, expected) {
+ results.total++;
+ var result = FnordMetric.util.searchMetricList(metrics, search_item);
+ for (var i = 0; i < result.length; i++) {
+ if (JSON.stringify(result[i]) != JSON.stringify(expected[i])) {
+ results.bad++;
+ console.log("Testing setURLQueryString expected "
+ + expected[i] + ", but was " + result[i]);
+ }
+ }
+ }
+ var m1 = {key : "/osx/load_avg_15m", labels : "", last_insert : 123};
+ var m2 = {key : "/osx/load 15m", labels : "" };
+ var m3 = {key : undefined, labels : ""};
+ var metrics = [m1, m2, m3];
+
+ test(metrics, "15", [m1, m2]);
+ test(metrics, 15, [m1, m2]);
+
+ }();
+
+ var testGenerateSQLQueryFromParams = function() {
+ function test(params, expected) {
+ var result = FnordMetric.util.generateSQLQueryFromParams(params);
+ result = result.replace(/(\n\r|\n|\r|\s)/gm, "");
+ if (result != expected) {
+ results.bad++;
+ console.log("Testing generateSQLQueryFromParams expected "
+ + expected + ", but was " + result);
+ }
+ }
+ // test XDOMAIN and WHERE clause
+ test(
+ {innerView : "metric",
+ innerViewValue : "status_codes",
+ view : "value",
+ by : "host",
+ columns : "host",
+ end_time : "1416263880000",
+ start_time : "1416260280000"},
+ "DRAWLINECHARTXDOMAINFROM_TIMESTAMP(1416260280),FROM_TIMESTAMP(1416263880)" +
+ "AXISBOTTOMAXISLEFTLEGENDTOPRIGHTINSIDE;SELECTtimeASx,valueASy,hostASseriesFROM`status_codes`" +
+ "WHEREtime>FROM_TIMESTAMP(1416260280)ANDtime.
*/
-/**
- *
- * TODOS:
- *
- * Query Playground:
- * - "embed this query" opens up a popup with html/js/ruby snippets
- * - prevent reload/navigation to other page (body onunload)
-* - stretch: display a [fake] loading bar
- * - nice to have: represent current chart and table, view in url --> renderResultPane
- *
- * Metric list view:
- * - write meaningful error messages
- *
- * - fix menuitems
- * - fix resize tooltip, vertical split
- * - wrong URL: redirect and display message
- * - fix back and for (push and pop states)
- *
- */
-
-
if (FnordMetric === undefined) {
FnordMetric = {};
}
@@ -38,12 +17,17 @@ if (FnordMetric.util === undefined) {
FnordMetric.util = {};
}
+/**
+ * extracts the params from the url
+ * @param qstr like metric_list?metric=/osx/load_avg_15m&view=value
+ */
FnordMetric.util.parseQueryString = function(qstr) {
var path;
var query_params = {};
if (qstr.indexOf("?") >= 0) {
- path = qstr.substr(0, qstr.indexOf("?"))
+ path = qstr.substr(0, qstr.indexOf("?"));
+ path = path.replace("#", "");
var params_str = qstr.substr(qstr.indexOf("?") + 1);
var raw_params = params_str.split('&');
@@ -54,13 +38,17 @@ FnordMetric.util.parseQueryString = function(qstr) {
query_params.innerViewValue = decodeURIComponent(param[1]);
for (var i = 1; i < raw_params.length; i++) {
- var param = raw_params[i].split('=');
- query_params[decodeURIComponent(param[0])] =
- decodeURIComponent(param[1]);
+ var param = (raw_params[i].split('=') != "undefined") ?
+ raw_params[i].split('=') : "";
+ if (param[0] != "undefined") {
+ query_params[decodeURIComponent(param[0])] =
+ (param[1] != "undefined") ?
+ decodeURIComponent(param[1]) : "";
+ }
}
} else {
- path = qstr;
+ path = qstr != "undefined" ? qstr : "";
}
return {
"path": path,
@@ -68,55 +56,48 @@ FnordMetric.util.parseQueryString = function(qstr) {
};
}
+/**
+ * builds a querystring from the query_params, attachs it to the hash
+ * and sets the url
+ * @param query_params should be like query_params in parseQueryString
+ * @param encode (boolean) determines if innerViewValue should be URIencoded
+ * @param push_state (boolena) determines if the url should be
+ * added to the browser's history
+ */
FnordMetric.util.setURLQueryString = function(hash, query_params, encode, push_state) {
- var path = "#"+hash+"?";
-
- path += query_params.innerView + "=";
- path += (encode)?
- encodeURIComponent(query_params.innerViewValue) :
- query_params.innerViewValue;
-
- for (var param in query_params) {
- if (param != "innerView" &&
- param != "innerViewValue" &&
- query_params[param] != undefined &&
- query_params[param].length > 0) {
-
- path +=
- "&" + param +
- "=" + query_params[param];
+ if (hash === undefined || hash === "undefined") {
+ window.location.hash = "";
+ return;
+ }
+ var path = "#" + hash;
+
+ if ("innerView" in query_params && query_params.innerView != undefined) {
+ path += "?" + query_params.innerView + "=";
+ path += (encode)?
+ encodeURIComponent(query_params.innerViewValue) :
+ query_params.innerViewValue;
+
+ for (var param in query_params) {
+ if (param != "innerView" &&
+ param != "innerViewValue" &&
+ query_params[param] != undefined &&
+ query_params[param].length > 0) {
+
+ path +=
+ "&" + param +
+ "=" + query_params[param];
+ }
}
}
if (push_state) {
+ console.log("push state");
window.history.pushState({url:path}, "#", path);
}
window.location.hash = path;
}
-//REMOVE??
-FnordMetric.util.setFragmentURL = function(hash, name, value, encode, push_state) {
- var path = window.location.pathname;
- var value = value;
- if (encode == true) {
- value = encodeURIComponent(value);
- }
- var hash =
- path + "#" + hash + "?" + name + "=" + value;
- window.location = hash;
- if (push_state == true) {
- window.history.pushState({url: hash}, "#", hash);
- }
-}
-
-FnordMetric.util.convertArrayToString = function(array) {
- var string = "";
- if (array.length > 0) {
- string = array.join(", ");
- }
- return string;
-}
/* simple loader foreground */
FnordMetric.util.displayLoader = function(elem) {
@@ -164,8 +145,6 @@ FnordMetric.util.displayErrorMessage = function(elem, msg) {
}
-
-
FnordMetric.util.renderPageHeader = function(text, elem) {
var header = document.createElement("h1");
header.className = "page_header";
@@ -174,13 +153,19 @@ FnordMetric.util.renderPageHeader = function(text, elem) {
elem.appendChild(header);
}
+/**
+ * creates a time description like
+ * '2 hours ago - Nov 8 2014 11:33:11
+ * @param timestamp unix ts in seconds, milli or microseconds
+ */
FnordMetric.util.parseTimestamp = function(timestamp) {
if (timestamp == 0) {
return "0";
}
+ var timestamp =
+ FnordMetric.util.convertToMilliTS(timestamp);
var time_str;
- var timestamp = timestamp / 1000;
var now = Date.now();
var date = new Date(timestamp);
@@ -227,7 +212,20 @@ FnordMetric.util.parseTimestamp = function(timestamp) {
return time_str;
}
+//FIXLAURA check all cases
+FnordMetric.util.convertToMilliTS = function(ts) {
+ var length = ts.toString().length;
+ if (length == 16) {
+ return (ts/1000);
+ } else if (length < 13 && length >= 10) {
+ return (ts * 1000);
+ } else {
+ return ts;
+ }
+}
+
FnordMetric.util.parseMilliTS = function(ts) {
+ var ts = FnordMetric.util.convertToMilliTS(ts);
if (ts < 1000) {
if (ts == 0) {
return " less than 1 millisecond";
@@ -252,17 +250,11 @@ FnordMetric.util.parseMilliTS = function(ts) {
return (ts + (ts == 1? " hour" : " hours"));
}
-FnordMetric.util.humanDateToMikroTS = function(date) {
- /* first version until datepicker is implemented */
- var ts;
- if (date == "NOW") {
- ts = Date.now();
+FnordMetric.util.humanCountRows = function(tables) {
+ if (tables == undefined) {
+ return "0 rows";
}
- return ts;
-}
-
-FnordMetric.util.humanCountRows = function(tables) {
var num = 0;
tables.map(function(table) {
num += table.rows.length;
@@ -270,7 +262,13 @@ FnordMetric.util.humanCountRows = function(tables) {
return (num == 1? num + " row" : num + " rows")
}
-
+/**
+ * sorts the metric list for a specific column
+ * @param metrics is an array of arrays
+ * @param column_index determines which 'column'
+ * ar array index should be sorted
+ * @param order can be asc or desc
+ */
FnordMetric.util.sortMetricList = function(metrics, column_index, order) {
function compare(a, b) {
if (a < b) {
@@ -350,12 +348,18 @@ FnordMetric.createButton = function(href, class_name, inner_HTML) {
return button;
}
+/**
+ * returns those metric objects whose key includes search_item
+ * @param metrics array of metric objects
+ */
FnordMetric.util.searchMetricList = function(metrics, search_item) {
//FIXME works but seems not to be the best solution
var data = [];
metrics.map(function(item) {
- if (item.key.indexOf(search_item) > -1) {
- data.push(item);
+ if (item.key != undefined) {
+ if (item.key.indexOf(search_item) > -1) {
+ data.push(item);
+ }
}
});
return data;
@@ -366,7 +370,7 @@ FnordMetric.util.htmlEscape = function(str) {
}
-/* returns all words that includes filter */
+/* returns all words that include filter */
FnordMetric.util.filterStringArray = function(strings, filter, limit) {
//FIXME ?
var data = [];
@@ -402,13 +406,16 @@ FnordMetric.util.milliSecondsToTimeString = function(seconds) {
}
-/* in singleMetricView */
+/**
+ * builds a ChartSQL query from url params
+ * @param params format as returned in parseQueryString
+ */
FnordMetric.util.generateSQLQueryFromParams = function(params) {
//FIX html escape
var table_ref = params.innerViewValue
var view = params.view;
/* column for rollups */
- var columns = params.columns.split(",");
+ var columns = params.columns;
var start_time = Math.round(params.start_time / 1000);
var end_time = Math.round(params.end_time / 1000);
var t_step = params.t_step;
@@ -417,26 +424,36 @@ FnordMetric.util.generateSQLQueryFromParams = function(params) {
var query;
var draw_stm =
- "DRAW LINECHART\n XDOMAIN\n FROM_TIMESTAMP(" +
- start_time + "),\n FROM_TIMESTAMP(" + end_time + ")"
- + "\n AXIS BOTTOM\n AXIS LEFT;\n\n";
+ "DRAW LINECHART\n ";
var select_expr = "SELECT\n time AS x,\n ";
var from_expr = "\n FROM\n";
var where_expr = "";
var group_expr = "";
var hasAggregation = false;
+ /* complement draw_stm */
+ if (!isNaN(start_time) && !isNaN(end_time)) {
+ draw_stm +=
+ "XDOMAIN\n FROM_TIMESTAMP(" + start_time +
+ "),\n FROM_TIMESTAMP(" + end_time + ")";
+ }
/* complete select_expr */
if (view == "value") {
- select_expr += "value as y ";
+ select_expr += "value AS y ";
} else if (view == "rollup_sum" || view == "rollup_count" || view == "rollup_mean") {
- draw_stm = "DRAW BARCHART\n AXIS BOTTOM\n AXIS LEFT;";
+ /* adapt draw stm */
+ draw_stm = "DRAW BARCHART\n ";
var func = (view.split("_"))[1];
- /* if the metric hasn't any labels total is selected */
- var column = (columns[0].length > 0)?
- ("`" + columns[0] + "`") : "'total'";
+ var column;
+ if (columns != undefined && (columns.split(","))[0].length > 0) {
+ columns = columns.split(",")
+ column = "`" + columns[0] + "`";
+ } else {
+ /* fallback if the metric hasn't any labels */
+ column = "'total'";
+ }
select_expr =
" SELECT " + column + " AS X, " + func + "(value) AS Y";
@@ -452,11 +469,17 @@ FnordMetric.util.generateSQLQueryFromParams = function(params) {
select_expr += ", " + series + " AS series";
}
+ /* complete draw stm */
+ draw_stm +=
+ "\n AXIS BOTTOM\n AXIS LEFT\n" +
+ " LEGEND TOP RIGHT INSIDE;\n\n";
+
+
/* complete from_expr */
from_expr += " `" + table_ref + "`\n";
/*complete where_expr */
- if (start_time != undefined && end_time != undefined) {
+ if (!isNaN(start_time) && !isNaN(end_time)) {
where_expr =
" WHERE\n time > FROM_TIMESTAMP(" + start_time + ")\n" +
" AND time < FROM_TIMESTAMP(" + end_time + ")";
@@ -468,14 +491,14 @@ FnordMetric.util.generateSQLQueryFromParams = function(params) {
group_expr = " GROUP ";
var hasGroupStm = false;
- if (t_step != undefined) {
+ if (t_window != undefined) {
hasGroupStm = true;
group_expr +=
- "OVER TIMEWINDOW(time, " + Math.round(t_step / 1000) + ",";
+ "OVER TIMEWINDOW(time, " + Math.round(t_window / 1000) + ",";
- group_expr += (t_window != undefined)?
- Math.round(t_window / 1000) : Math.round(t_step / 1000);
+ group_expr += (t_step != undefined)?
+ Math.round(t_step / 1000) : Math.round(t_window / 1000);
group_expr+= ")";
@@ -494,10 +517,9 @@ FnordMetric.util.generateSQLQueryFromParams = function(params) {
}
- query =
+ query =
draw_stm + select_expr + from_expr +
where_expr + group_expr + ";";
-
return query;
}
@@ -522,7 +544,7 @@ FnordMetric.util.getMonthStr = function(index) {
FnordMetric.util.isNumKey = function(keycode) {
return (
- (keycode >= 48 && keycode <= 57) || (keycode >= 96 && keycode <= 105));
+ (keycode >= 48 && keycode <= 57));
}
/* tab, arrow-left, arrow-right, deletekeys */
@@ -535,59 +557,14 @@ FnordMetric.util.isNavKey = function(keycode) {
keycode == 46);
}
-
-FnordMetric.util.validatedTimeInput = function (time_input, type) {
- var input = time_input.value;
-
- time_input.addEventListener('keydown', function(e) {
- if (FnordMetric.util.isNumKey(e.keyCode)) {
- var n = String.fromCharCode(e.keyCode);
- input = time_input.value;
-
- if (type == "hour") {
- if (input.length == 0) {
- if (n >= 0 && n <= 2) {
- input = n;
- time_input.value = n;
- } else{
- e.preventDefault();
- }
- } else if (input.length == 1) {
- console.log(input);
- if (input < 2 || (input == 2 && n < 4)) {
- input = input * 10 + n;
- time_input.value += n;
- } else {
- e.preventDefault();
- }
- } else {
- e.preventDefault();
- }
-
- } else if (type == "minute") {
- if (input.length == 0) {
- if (n >= 0 && n <= 5) {
- input = n;
- time_input.value = n;
- } else {
- e.preventDefault();
- }
- } else if (input.length == 1) {
- input = input * 10 + n;
- time_input.value += n;
- } else {
- e.preventDefault();
- }
- } else {
- e.preventDefault();
- }
- }
-
- if (!FnordMetric.util.isNavKey(e.keyCode)) {
+FnordMetric.util.validatedTimeInput = function(time_input) {
+ time_input.maxLength = "2";
+ time_input.addEventListener('keypress', function(e) {
+ if (!FnordMetric.util.isNavKey(e.keyCode) &&
+ !FnordMetric.util.isNumKey(e.keyCode)) {
e.preventDefault();
}
- }, false);
-
+ },false);
}
FnordMetric.util.appendLeadingZero = function (num) {