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) {