mirror of
https://github.com/prometheus/prometheus.git
synced 2025-02-21 03:16:00 -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;
|
||||
}
|
||||
|
||||
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 {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,9 @@ var graphTemplate;
|
|||
|
||||
var SECOND = 1000;
|
||||
|
||||
/**
|
||||
* Graph
|
||||
*/
|
||||
Prometheus.Graph = function(element, options, handleChange, handleRemove) {
|
||||
this.el = element;
|
||||
this.graphHTML = null;
|
||||
|
@ -61,8 +64,8 @@ Prometheus.Graph.prototype.initialize = function() {
|
|||
|
||||
self.expr = graphWrapper.find("textarea[name=expr]");
|
||||
self.expr.keypress(function(e) {
|
||||
// Enter was pressed without the shift key.
|
||||
if (e.which == 13 && !e.shiftKey) {
|
||||
const enter = 13;
|
||||
if (e.which == enter && !e.shiftKey) {
|
||||
self.queryForm.submit();
|
||||
e.preventDefault();
|
||||
}
|
||||
|
@ -183,8 +186,14 @@ Prometheus.Graph.prototype.initialize = function() {
|
|||
});
|
||||
|
||||
self.checkTimeDrift();
|
||||
// initialize query history
|
||||
if (!localStorage.getItem("history")) {
|
||||
localStorage.setItem("history", JSON.stringify([]));
|
||||
}
|
||||
self.populateInsertableMetrics();
|
||||
|
||||
queryHistory.bindHistoryEvents(self);
|
||||
|
||||
if (self.expr.val()) {
|
||||
self.submitQuery();
|
||||
}
|
||||
|
@ -230,9 +239,10 @@ Prometheus.Graph.prototype.populateInsertableMetrics = function() {
|
|||
self.showError("Error loading available metrics!");
|
||||
return;
|
||||
}
|
||||
var metrics = json.data;
|
||||
for (var i = 0; i < metrics.length; i++) {
|
||||
self.insertMetric[0].options.add(new Option(metrics[i], metrics[i]));
|
||||
|
||||
pageConfig.allMetrics = json.data; // todo: do we need self.allMetrics? Or can it just live on the page
|
||||
for (var i = 0; i < pageConfig.allMetrics.length; i++) {
|
||||
self.insertMetric[0].options.add(new Option(pageConfig.allMetrics[i], pageConfig.allMetrics[i]));
|
||||
}
|
||||
|
||||
self.fuzzyResult = {
|
||||
|
@ -241,41 +251,7 @@ Prometheus.Graph.prototype.populateInsertableMetrics = function() {
|
|||
map: {}
|
||||
}
|
||||
|
||||
self.expr.typeahead({
|
||||
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();
|
||||
self.initTypeahead(self);
|
||||
},
|
||||
error: function() {
|
||||
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() {
|
||||
var self = this;
|
||||
var options = {};
|
||||
|
@ -429,6 +446,7 @@ Prometheus.Graph.prototype.submitQuery = function() {
|
|||
self.showError(json.error);
|
||||
return;
|
||||
}
|
||||
queryHistory.handleHistory(self);
|
||||
success(json.data, textStatus);
|
||||
},
|
||||
error: function(xhr, resp) {
|
||||
|
@ -807,25 +825,15 @@ Prometheus.Graph.prototype.formatKMBT = function(y) {
|
|||
}
|
||||
}
|
||||
|
||||
function escapeHTML(string) {
|
||||
var entityMap = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
"/": '/'
|
||||
};
|
||||
|
||||
return String(string).replace(/[&<>"'\/]/g, function (s) {
|
||||
return entityMap[s];
|
||||
});
|
||||
}
|
||||
|
||||
Prometheus.Page = function() {
|
||||
this.graphs = [];
|
||||
/**
|
||||
* Page
|
||||
*/
|
||||
const pageConfig = {
|
||||
graphs: []
|
||||
};
|
||||
|
||||
Prometheus.Page = function() {};
|
||||
|
||||
Prometheus.Page.prototype.init = function() {
|
||||
var graphOptions = this.parseURL();
|
||||
if (graphOptions.length === 0) {
|
||||
|
@ -833,7 +841,6 @@ Prometheus.Page.prototype.init = function() {
|
|||
}
|
||||
|
||||
graphOptions.forEach(this.addGraph, this);
|
||||
|
||||
$("#add_graph").click(this.addGraph.bind(this, {}));
|
||||
};
|
||||
|
||||
|
@ -855,7 +862,9 @@ Prometheus.Page.prototype.addGraph = function(options) {
|
|||
this.removeGraph.bind(this)
|
||||
);
|
||||
|
||||
this.graphs.push(graph);
|
||||
// this.graphs.push(graph);
|
||||
pageConfig.graphs.push(graph);
|
||||
|
||||
$(window).resize(function() {
|
||||
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
|
||||
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 queryParamHelper = new Prometheus.Page.QueryParamHelper();
|
||||
var queryObject = queryParamHelper.generateQueryObject(graphOptions, index);
|
||||
|
@ -874,7 +883,7 @@ Prometheus.Page.prototype.updateURL = function() {
|
|||
};
|
||||
|
||||
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() {};
|
||||
|
@ -950,29 +959,6 @@ Prometheus.Page.QueryParamHelper.prototype.generateQueryObject = function(graphO
|
|||
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)
|
||||
// are added only for backward compatibility to old query format.
|
||||
function isDeprecatedGraphURL() {
|
||||
|
@ -1004,4 +990,108 @@ function redirectToMigratedURL() {
|
|||
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);
|
||||
|
|
@ -17,14 +17,19 @@
|
|||
<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/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>
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<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 class="container-fluid">
|
||||
<div><input class="btn btn-primary" type="submit" value="Add Graph" id="add_graph"></div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue