mirror of
https://github.com/prometheus/prometheus.git
synced 2025-03-05 20:59:13 -08:00
Implement browsing of previous query expressions (#3486)
This commit is contained in:
parent
78625f85a7
commit
a8ff643464
File diff suppressed because one or more lines are too long
|
@ -6,6 +6,30 @@ body {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.query-history {
|
||||||
|
font-size: 0.8em;
|
||||||
|
padding-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.query-history:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.query-history button {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.query-history.is-checked {
|
||||||
|
color: #286090;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.page-options {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
.graph_wrapper {
|
.graph_wrapper {
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,9 @@ var graphTemplate;
|
||||||
|
|
||||||
var SECOND = 1000;
|
var SECOND = 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Graph
|
||||||
|
*/
|
||||||
Prometheus.Graph = function(element, options, handleChange, handleRemove) {
|
Prometheus.Graph = function(element, options, handleChange, handleRemove) {
|
||||||
this.el = element;
|
this.el = element;
|
||||||
this.graphHTML = null;
|
this.graphHTML = null;
|
||||||
|
@ -61,8 +64,8 @@ Prometheus.Graph.prototype.initialize = function() {
|
||||||
|
|
||||||
self.expr = graphWrapper.find("textarea[name=expr]");
|
self.expr = graphWrapper.find("textarea[name=expr]");
|
||||||
self.expr.keypress(function(e) {
|
self.expr.keypress(function(e) {
|
||||||
// Enter was pressed without the shift key.
|
const enter = 13;
|
||||||
if (e.which == 13 && !e.shiftKey) {
|
if (e.which == enter && !e.shiftKey) {
|
||||||
self.queryForm.submit();
|
self.queryForm.submit();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
@ -183,8 +186,14 @@ Prometheus.Graph.prototype.initialize = function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
self.checkTimeDrift();
|
self.checkTimeDrift();
|
||||||
|
// initialize query history
|
||||||
|
if (!localStorage.getItem("history")) {
|
||||||
|
localStorage.setItem("history", JSON.stringify([]));
|
||||||
|
}
|
||||||
self.populateInsertableMetrics();
|
self.populateInsertableMetrics();
|
||||||
|
|
||||||
|
queryHistory.bindHistoryEvents(self);
|
||||||
|
|
||||||
if (self.expr.val()) {
|
if (self.expr.val()) {
|
||||||
self.submitQuery();
|
self.submitQuery();
|
||||||
}
|
}
|
||||||
|
@ -230,9 +239,10 @@ Prometheus.Graph.prototype.populateInsertableMetrics = function() {
|
||||||
self.showError("Error loading available metrics!");
|
self.showError("Error loading available metrics!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var metrics = json.data;
|
|
||||||
for (var i = 0; i < metrics.length; i++) {
|
pageConfig.allMetrics = json.data; // todo: do we need self.allMetrics? Or can it just live on the page
|
||||||
self.insertMetric[0].options.add(new Option(metrics[i], metrics[i]));
|
for (var i = 0; i < pageConfig.allMetrics.length; i++) {
|
||||||
|
self.insertMetric[0].options.add(new Option(pageConfig.allMetrics[i], pageConfig.allMetrics[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.fuzzyResult = {
|
self.fuzzyResult = {
|
||||||
|
@ -241,41 +251,7 @@ Prometheus.Graph.prototype.populateInsertableMetrics = function() {
|
||||||
map: {}
|
map: {}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.expr.typeahead({
|
self.initTypeahead(self);
|
||||||
source: metrics,
|
|
||||||
items: "all",
|
|
||||||
matcher: function(item) {
|
|
||||||
// If we have result for current query, skip
|
|
||||||
if (self.fuzzyResult.query !== this.query) {
|
|
||||||
self.fuzzyResult.query = this.query;
|
|
||||||
self.fuzzyResult.map = {};
|
|
||||||
self.fuzzyResult.result = fuzzy.filter(this.query.replace(/ /g, ''), metrics, {
|
|
||||||
pre: '<strong>',
|
|
||||||
post: '</strong>'
|
|
||||||
});
|
|
||||||
self.fuzzyResult.result.forEach(function(r) {
|
|
||||||
self.fuzzyResult.map[r.original] = r;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return item in self.fuzzyResult.map;
|
|
||||||
},
|
|
||||||
|
|
||||||
sorter: function(items) {
|
|
||||||
items.sort(function(a,b) {
|
|
||||||
var i = self.fuzzyResult.map[b].score - self.fuzzyResult.map[a].score;
|
|
||||||
return i === 0 ? a.localeCompare(b) : i;
|
|
||||||
});
|
|
||||||
return items;
|
|
||||||
},
|
|
||||||
|
|
||||||
highlighter: function (item) {
|
|
||||||
return $('<div>' + self.fuzzyResult.map[item].string + '</div>')
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// This needs to happen after attaching the typeahead plugin, as it
|
|
||||||
// otherwise breaks the typeahead functionality.
|
|
||||||
self.expr.focus();
|
|
||||||
},
|
},
|
||||||
error: function() {
|
error: function() {
|
||||||
self.showError("Error loading available metrics!");
|
self.showError("Error loading available metrics!");
|
||||||
|
@ -283,6 +259,47 @@ Prometheus.Graph.prototype.populateInsertableMetrics = function() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Prometheus.Graph.prototype.initTypeahead = function(self) {
|
||||||
|
const historyIsChecked = $("div.query-history").hasClass("is-checked");
|
||||||
|
const source = historyIsChecked ? pageConfig.allMetrics.concat(JSON.parse(localStorage.getItem("history"))) : pageConfig.allMetrics;
|
||||||
|
|
||||||
|
self.expr.typeahead({
|
||||||
|
source,
|
||||||
|
items: "all",
|
||||||
|
matcher: function (item) {
|
||||||
|
// If we have result for current query, skip
|
||||||
|
if (self.fuzzyResult.query !== this.query) {
|
||||||
|
self.fuzzyResult.query = this.query;
|
||||||
|
self.fuzzyResult.map = {};
|
||||||
|
self.fuzzyResult.result = fuzzy.filter(this.query.replace(/ /g, ""), this.source, {
|
||||||
|
pre: "<strong>",
|
||||||
|
post: "</strong>"
|
||||||
|
});
|
||||||
|
self.fuzzyResult.result.forEach(function(r) {
|
||||||
|
self.fuzzyResult.map[r.original] = r;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return item in self.fuzzyResult.map;
|
||||||
|
},
|
||||||
|
|
||||||
|
sorter: function (items) {
|
||||||
|
items.sort(function(a,b) {
|
||||||
|
var i = self.fuzzyResult.map[b].score - self.fuzzyResult.map[a].score;
|
||||||
|
return i === 0 ? a.localeCompare(b) : i;
|
||||||
|
});
|
||||||
|
return items;
|
||||||
|
},
|
||||||
|
|
||||||
|
highlighter: function (item) {
|
||||||
|
return $("<div>" + self.fuzzyResult.map[item].string + "</div>");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// This needs to happen after attaching the typeahead plugin, as it
|
||||||
|
// otherwise breaks the typeahead functionality.
|
||||||
|
self.expr.focus();
|
||||||
|
}
|
||||||
|
|
||||||
Prometheus.Graph.prototype.getOptions = function() {
|
Prometheus.Graph.prototype.getOptions = function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
var options = {};
|
var options = {};
|
||||||
|
@ -429,6 +446,7 @@ Prometheus.Graph.prototype.submitQuery = function() {
|
||||||
self.showError(json.error);
|
self.showError(json.error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
queryHistory.handleHistory(self);
|
||||||
success(json.data, textStatus);
|
success(json.data, textStatus);
|
||||||
},
|
},
|
||||||
error: function(xhr, resp) {
|
error: function(xhr, resp) {
|
||||||
|
@ -807,25 +825,15 @@ Prometheus.Graph.prototype.formatKMBT = function(y) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeHTML(string) {
|
/**
|
||||||
var entityMap = {
|
* Page
|
||||||
"&": "&",
|
*/
|
||||||
"<": "<",
|
const pageConfig = {
|
||||||
">": ">",
|
graphs: []
|
||||||
'"': '"',
|
|
||||||
"'": ''',
|
|
||||||
"/": '/'
|
|
||||||
};
|
|
||||||
|
|
||||||
return String(string).replace(/[&<>"'\/]/g, function (s) {
|
|
||||||
return entityMap[s];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Prometheus.Page = function() {
|
|
||||||
this.graphs = [];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Prometheus.Page = function() {};
|
||||||
|
|
||||||
Prometheus.Page.prototype.init = function() {
|
Prometheus.Page.prototype.init = function() {
|
||||||
var graphOptions = this.parseURL();
|
var graphOptions = this.parseURL();
|
||||||
if (graphOptions.length === 0) {
|
if (graphOptions.length === 0) {
|
||||||
|
@ -833,7 +841,6 @@ Prometheus.Page.prototype.init = function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
graphOptions.forEach(this.addGraph, this);
|
graphOptions.forEach(this.addGraph, this);
|
||||||
|
|
||||||
$("#add_graph").click(this.addGraph.bind(this, {}));
|
$("#add_graph").click(this.addGraph.bind(this, {}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -855,7 +862,9 @@ Prometheus.Page.prototype.addGraph = function(options) {
|
||||||
this.removeGraph.bind(this)
|
this.removeGraph.bind(this)
|
||||||
);
|
);
|
||||||
|
|
||||||
this.graphs.push(graph);
|
// this.graphs.push(graph);
|
||||||
|
pageConfig.graphs.push(graph);
|
||||||
|
|
||||||
$(window).resize(function() {
|
$(window).resize(function() {
|
||||||
graph.resizeGraph();
|
graph.resizeGraph();
|
||||||
});
|
});
|
||||||
|
@ -863,7 +872,7 @@ Prometheus.Page.prototype.addGraph = function(options) {
|
||||||
|
|
||||||
// NOTE: This needs to be kept in sync with /util/strutil/strconv.go:GraphLinkForExpression
|
// NOTE: This needs to be kept in sync with /util/strutil/strconv.go:GraphLinkForExpression
|
||||||
Prometheus.Page.prototype.updateURL = function() {
|
Prometheus.Page.prototype.updateURL = function() {
|
||||||
var queryString = this.graphs.map(function(graph, index) {
|
var queryString = pageConfig.graphs.map(function(graph, index) {
|
||||||
var graphOptions = graph.getOptions();
|
var graphOptions = graph.getOptions();
|
||||||
var queryParamHelper = new Prometheus.Page.QueryParamHelper();
|
var queryParamHelper = new Prometheus.Page.QueryParamHelper();
|
||||||
var queryObject = queryParamHelper.generateQueryObject(graphOptions, index);
|
var queryObject = queryParamHelper.generateQueryObject(graphOptions, index);
|
||||||
|
@ -874,7 +883,7 @@ Prometheus.Page.prototype.updateURL = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
Prometheus.Page.prototype.removeGraph = function(graph) {
|
Prometheus.Page.prototype.removeGraph = function(graph) {
|
||||||
this.graphs = this.graphs.filter(function(g) {return g !== graph});
|
pageConfig.graphs = pageConfig.graphs.filter(function(g) {return g !== graph});
|
||||||
};
|
};
|
||||||
|
|
||||||
Prometheus.Page.QueryParamHelper = function() {};
|
Prometheus.Page.QueryParamHelper = function() {};
|
||||||
|
@ -950,29 +959,6 @@ Prometheus.Page.QueryParamHelper.prototype.generateQueryObject = function(graphO
|
||||||
return queryObject;
|
return queryObject;
|
||||||
};
|
};
|
||||||
|
|
||||||
function init() {
|
|
||||||
$.ajaxSetup({
|
|
||||||
cache: false
|
|
||||||
});
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: PATH_PREFIX + "/static/js/graph_template.handlebar?v=" + BUILD_VERSION,
|
|
||||||
success: function(data) {
|
|
||||||
|
|
||||||
graphTemplate = data;
|
|
||||||
Mustache.parse(data);
|
|
||||||
if (isDeprecatedGraphURL()) {
|
|
||||||
redirectToMigratedURL();
|
|
||||||
} else {
|
|
||||||
var Page = new Prometheus.Page();
|
|
||||||
Page.init();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// These two methods (isDeprecatedGraphURL and redirectToMigratedURL)
|
// These two methods (isDeprecatedGraphURL and redirectToMigratedURL)
|
||||||
// are added only for backward compatibility to old query format.
|
// are added only for backward compatibility to old query format.
|
||||||
function isDeprecatedGraphURL() {
|
function isDeprecatedGraphURL() {
|
||||||
|
@ -1004,4 +990,108 @@ function redirectToMigratedURL() {
|
||||||
window.location = PATH_PREFIX + "/graph?" + query;
|
window.location = PATH_PREFIX + "/graph?" + query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query History helper functions
|
||||||
|
* **/
|
||||||
|
const queryHistory = {
|
||||||
|
bindHistoryEvents: function(graph) {
|
||||||
|
const targetEl = $('div.query-history');
|
||||||
|
const icon = $(targetEl).children('i');
|
||||||
|
targetEl.off('click');
|
||||||
|
|
||||||
|
if (JSON.parse(localStorage.getItem('enable-query-history'))) {
|
||||||
|
this.toggleOn(targetEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
targetEl.on('click', function() {
|
||||||
|
if (icon.hasClass('glyphicon-unchecked')) {
|
||||||
|
queryHistory.toggleOn(targetEl);
|
||||||
|
} else if (icon.hasClass('glyphicon-check')) {
|
||||||
|
queryHistory.toggleOff(targetEl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
handleHistory: function(graph) {
|
||||||
|
const query = graph.expr.val();
|
||||||
|
const isSimpleMetric = pageConfig.allMetrics.indexOf(query) !== -1;
|
||||||
|
if (isSimpleMetric) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parsedQueryHistory = JSON.parse(localStorage.getItem('history'));
|
||||||
|
const hasStoredQuery = parsedQueryHistory.indexOf(query) !== -1;
|
||||||
|
if (hasStoredQuery) {
|
||||||
|
parsedQueryHistory.splice(parsedQueryHistory.indexOf(query), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedQueryHistory.push(query);
|
||||||
|
const queryCount = parsedQueryHistory.length;
|
||||||
|
parsedQueryHistory = parsedQueryHistory.slice(queryCount - 50, queryCount);
|
||||||
|
|
||||||
|
localStorage.setItem('history', JSON.stringify(parsedQueryHistory));
|
||||||
|
|
||||||
|
this.updateTypeaheadMetricSet(parsedQueryHistory);
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleOn: function(targetEl) {
|
||||||
|
this.updateTypeaheadMetricSet(JSON.parse(localStorage.getItem('history')));
|
||||||
|
|
||||||
|
$(targetEl).children('i').removeClass('glyphicon-unchecked').addClass('glyphicon-check');
|
||||||
|
targetEl.addClass('is-checked');
|
||||||
|
localStorage.setItem('enable-query-history', true);
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleOff: function(targetEl) {
|
||||||
|
this.updateTypeaheadMetricSet();
|
||||||
|
|
||||||
|
$(targetEl).children('i').removeClass('glyphicon-check').addClass('glyphicon-unchecked');
|
||||||
|
targetEl.removeClass('is-checked');
|
||||||
|
localStorage.setItem('enable-query-history', false);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateTypeaheadMetricSet: function(metricSet) {
|
||||||
|
pageConfig.graphs.forEach(function(graph) {
|
||||||
|
graph.expr.data('typeahead').source = metricSet ? pageConfig.allMetrics.concat(metricSet) : pageConfig.allMetrics;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function escapeHTML(string) {
|
||||||
|
var entityMap = {
|
||||||
|
"&": "&",
|
||||||
|
"<": "<",
|
||||||
|
">": ">",
|
||||||
|
'"': '"',
|
||||||
|
"'": ''',
|
||||||
|
"/": '/'
|
||||||
|
};
|
||||||
|
|
||||||
|
return String(string).replace(/[&<>"'\/]/g, function (s) {
|
||||||
|
return entityMap[s];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
$.ajaxSetup({
|
||||||
|
cache: false
|
||||||
|
});
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: PATH_PREFIX + "/static/js/graph/graph_template.handlebar?v=" + BUILD_VERSION,
|
||||||
|
success: function(data) {
|
||||||
|
|
||||||
|
graphTemplate = data;
|
||||||
|
Mustache.parse(data);
|
||||||
|
if (isDeprecatedGraphURL()) {
|
||||||
|
redirectToMigratedURL();
|
||||||
|
} else {
|
||||||
|
var Page = new Prometheus.Page();
|
||||||
|
Page.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$(init);
|
$(init);
|
||||||
|
|
|
@ -17,14 +17,19 @@
|
||||||
<script src="{{ pathPrefix }}/static/vendor/js/jquery.selection.js?v={{ buildVersion }}"></script>
|
<script src="{{ pathPrefix }}/static/vendor/js/jquery.selection.js?v={{ buildVersion }}"></script>
|
||||||
<script src="{{ pathPrefix }}/static/vendor/js/jquery.hotkeys.js?v={{ buildVersion }}"></script>
|
<script src="{{ pathPrefix }}/static/vendor/js/jquery.hotkeys.js?v={{ buildVersion }}"></script>
|
||||||
|
|
||||||
<script src="{{ pathPrefix }}/static/js/graph.js?v={{ buildVersion }}"></script>
|
<script src="{{ pathPrefix }}/static/js/graph/index.js?v={{ buildVersion }}"></script>
|
||||||
|
|
||||||
<script id="graph_template" type="text/x-handlebars-template"></script>
|
<script id="graph_template" type="text/x-handlebars-template"></script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
<div id="graph_container" class="container-fluid">
|
<div id="graph_container" class="container-fluid">
|
||||||
|
<div class="query-history">
|
||||||
|
<i class="glyphicon glyphicon-unchecked"></i>
|
||||||
|
<button type="button" class="search-history" title="search previous queries">Enable query history</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div><input class="btn btn-primary" type="submit" value="Add Graph" id="add_graph"></div>
|
<div><input class="btn btn-primary" type="submit" value="Add Graph" id="add_graph"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue