Commit dda1cf1a by Torkel Ödegaard

Merge branch 'ace-editor'

parents 78ea1ea7 bdb1cfb0
...@@ -63,6 +63,7 @@ ...@@ -63,6 +63,7 @@
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"ace-builds": "^1.2.8",
"eventemitter3": "^2.0.2", "eventemitter3": "^2.0.2",
"gaze": "^1.1.2", "gaze": "^1.1.2",
"grunt-jscs": "3.0.1", "grunt-jscs": "3.0.1",
......
/**
* codeEditor directive based on Ace code editor
* https://github.com/ajaxorg/ace
*
* Basic usage:
* <code-editor content="ctrl.target.query" on-change="ctrl.panelCtrl.refresh()"
* data-mode="sql" data-show-gutter>
* </code-editor>
*
* Params:
* content: Editor content.
* onChange: Function called on content change (invoked on editor blur, ctrl+enter, not on every change).
* getCompleter: Function returned external completer. Completer is an object implemented getCompletions() method,
* see Prometheus Data Source implementation for details.
*
* Some Ace editor options available via data-* attributes:
* data-mode - Language mode (text, sql, javascript, etc.). Default is 'text'.
* data-theme - Editor theme (eg 'solarized_dark').
* data-max-lines - Max editor height in lines. Editor grows automatically from 1 to maxLines.
* data-show-gutter - Show gutter (contains line numbers and additional info).
* data-tab-size - Tab size, default is 2.
* data-behaviours-enabled - Specifies whether to use behaviors or not. "Behaviors" in this case is the auto-pairing of
* special characters, like quotation marks, parenthesis, or brackets.
*
* Keybindings:
* Ctrl-Enter (Command-Enter): run onChange() function
*/
///<reference path="../../../headers/common.d.ts" />
import _ from 'lodash';
import coreModule from 'app/core/core_module';
import config from 'app/core/config';
import ace from 'ace';
const ACE_SRC_BASE = "public/vendor/npm/ace-builds/src-noconflict/";
const DEFAULT_THEME_DARK = "grafana-dark";
const DEFAULT_THEME_LIGHT = "textmate";
const DEFAULT_MODE = "text";
const DEFAULT_MAX_LINES = 10;
const DEFAULT_TAB_SIZE = 2;
const DEFAULT_BEHAVIOURS = true;
const GRAFANA_MODULES = ['mode-prometheus', 'snippets-prometheus', 'theme-grafana-dark'];
const GRAFANA_MODULE_BASE = "public/app/core/components/code_editor/";
// Trick for loading additional modules
function setModuleUrl(moduleType, name) {
let baseUrl = ACE_SRC_BASE;
let aceModeName = `ace/${moduleType}/${name}`;
let moduleName = `${moduleType}-${name}`;
let componentName = `${moduleName}.js`;
if (_.includes(GRAFANA_MODULES, moduleName)) {
baseUrl = GRAFANA_MODULE_BASE;
}
if (moduleType === 'snippets') {
componentName = `${moduleType}/${name}.js`;
}
ace.config.setModuleUrl(aceModeName, baseUrl + componentName);
}
setModuleUrl("ext", "language_tools");
setModuleUrl("mode", "text");
setModuleUrl("snippets", "text");
let editorTemplate = `<div></div>`;
function link(scope, elem, attrs) {
let lightTheme = config.bootData.user.lightTheme;
let default_theme = lightTheme ? DEFAULT_THEME_LIGHT : DEFAULT_THEME_DARK;
// Options
let langMode = attrs.mode || DEFAULT_MODE;
let maxLines = attrs.maxLines || DEFAULT_MAX_LINES;
let showGutter = attrs.showGutter !== undefined;
let theme = attrs.theme || default_theme;
let tabSize = attrs.tabSize || DEFAULT_TAB_SIZE;
let behavioursEnabled = attrs.behavioursEnabled ? attrs.behavioursEnabled === 'true' : DEFAULT_BEHAVIOURS;
// Initialize editor
let aceElem = elem.get(0);
let codeEditor = ace.edit(aceElem);
let editorSession = codeEditor.getSession();
let editorOptions = {
maxLines: maxLines,
showGutter: showGutter,
tabSize: tabSize,
behavioursEnabled: behavioursEnabled,
highlightActiveLine: false,
showPrintMargin: false,
autoScrollEditorIntoView: true // this is needed if editor is inside scrollable page
};
// Set options
codeEditor.setOptions(editorOptions);
// disable depreacation warning
codeEditor.$blockScrolling = Infinity;
// Padding hacks
codeEditor.renderer.setScrollMargin(15, 15);
codeEditor.renderer.setPadding(10);
setThemeMode(theme);
setLangMode(langMode);
setEditorContent(scope.content);
// Add classes
elem.addClass("gf-code-editor");
let textarea = elem.find("textarea");
textarea.addClass('gf-form-input');
// Event handlers
editorSession.on('change', (e) => {
scope.$apply(() => {
let newValue = codeEditor.getValue();
scope.content = newValue;
});
});
// Sync with outer scope - update editor content if model has been changed from outside of directive.
scope.$watch('content', (newValue, oldValue) => {
let editorValue = codeEditor.getValue();
if (newValue !== editorValue && newValue !== oldValue) {
scope.$$postDigest(function() {
setEditorContent(newValue);
});
}
});
codeEditor.on('blur', () => {
scope.onChange();
});
scope.$on("$destroy", () => {
codeEditor.destroy();
});
// Keybindings
codeEditor.commands.addCommand({
name: 'executeQuery',
bindKey: {win: 'Ctrl-Enter', mac: 'Command-Enter'},
exec: () => {
scope.onChange();
}
});
function setLangMode(lang) {
let aceModeName = `ace/mode/${lang}`;
setModuleUrl("mode", lang);
setModuleUrl("snippets", lang);
editorSession.setMode(aceModeName);
ace.config.loadModule("ace/ext/language_tools", (language_tools) => {
codeEditor.setOptions({
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
enableSnippets: true
});
console.log('getting completer', lang);
if (scope.getCompleter()) {
// make copy of array as ace seems to share completers array between instances
codeEditor.completers = codeEditor.completers.slice();
codeEditor.completers.push(scope.getCompleter());
}
});
}
function setThemeMode(theme) {
setModuleUrl("theme", theme);
let themeModule = `ace/theme/${theme}`;
ace.config.loadModule(themeModule, (theme_module) => {
// Check is theme light or dark and fix if needed
let lightTheme = config.bootData.user.lightTheme;
let fixedTheme = theme;
if (lightTheme && theme_module.isDark) {
fixedTheme = DEFAULT_THEME_LIGHT;
} else if (!lightTheme && !theme_module.isDark) {
fixedTheme = DEFAULT_THEME_DARK;
}
setModuleUrl("theme", fixedTheme);
themeModule = `ace/theme/${fixedTheme}`;
codeEditor.setTheme(themeModule);
elem.addClass("gf-code-editor--theme-loaded");
});
}
function setEditorContent(value) {
codeEditor.setValue(value);
codeEditor.clearSelection();
}
}
export function codeEditorDirective() {
return {
restrict: 'E',
template: editorTemplate,
scope: {
content: "=",
onChange: "&",
getCompleter: "&"
},
link: link
};
}
coreModule.directive('codeEditor', codeEditorDirective);
// jshint ignore: start
// jscs: disable
ace.define("ace/snippets/prometheus",["require","exports","module"], function(require, exports, module) {
"use strict";
// exports.snippetText = "# rate\n\
// snippet r\n\
// rate(${1:metric}[${2:range}])\n\
// ";
exports.snippets = [
{
"content": "rate(${1:metric}[${2:range}])",
"name": "rate()",
"scope": "prometheus",
"tabTrigger": "r"
}
];
exports.scope = "prometheus";
});
/* jshint ignore:start */
ace.define("ace/theme/grafana-dark",["require","exports","module","ace/lib/dom"], function(require, exports, module) {
"use strict";
exports.isDark = true;
exports.cssClass = "gf-code-dark";
exports.cssText = ".gf-code-dark .ace_gutter {\
background: #2f3129;\
color: #8f908a\
}\
.gf-code-dark .ace_print-margin {\
width: 1px;\
background: #555651\
}\
.gf-code-dark {\
background-color: #111;\
color: #e0e0e0\
}\
.gf-code-dark .ace_cursor {\
color: #f8f8f0\
}\
.gf-code-dark .ace_marker-layer .ace_selection {\
background: #49483e\
}\
.gf-code-dark.ace_multiselect .ace_selection.ace_start {\
box-shadow: 0 0 3px 0px #272822;\
}\
.gf-code-dark .ace_marker-layer .ace_step {\
background: rgb(102, 82, 0)\
}\
.gf-code-dark .ace_marker-layer .ace_bracket {\
margin: -1px 0 0 -1px;\
border: 1px solid #49483e\
}\
.gf-code-dark .ace_marker-layer .ace_active-line {\
background: #202020\
}\
.gf-code-dark .ace_gutter-active-line {\
background-color: #272727\
}\
.gf-code-dark .ace_marker-layer .ace_selected-word {\
border: 1px solid #49483e\
}\
.gf-code-dark .ace_invisible {\
color: #52524d\
}\
.gf-code-dark .ace_entity.ace_name.ace_tag,\
.gf-code-dark .ace_keyword,\
.gf-code-dark .ace_meta.ace_tag,\
.gf-code-dark .ace_storage {\
color: #66d9ef\
}\
.gf-code-dark .ace_punctuation,\
.gf-code-dark .ace_punctuation.ace_tag {\
color: #fff\
}\
.gf-code-dark .ace_constant.ace_character,\
.gf-code-dark .ace_constant.ace_language,\
.gf-code-dark .ace_constant.ace_numeric,\
.gf-code-dark .ace_constant.ace_other {\
color: #fe85fc\
}\
.gf-code-dark .ace_invalid {\
color: #f8f8f0;\
background-color: #f92672\
}\
.gf-code-dark .ace_invalid.ace_deprecated {\
color: #f8f8f0;\
background-color: #ae81ff\
}\
.gf-code-dark .ace_support.ace_constant,\
.gf-code-dark .ace_support.ace_function {\
color: #59e6e3\
}\
.gf-code-dark .ace_fold {\
background-color: #a6e22e;\
border-color: #f8f8f2\
}\
.gf-code-dark .ace_storage.ace_type,\
.gf-code-dark .ace_support.ace_class,\
.gf-code-dark .ace_support.ace_type {\
font-style: italic;\
color: #66d9ef\
}\
.gf-code-dark .ace_entity.ace_name.ace_function,\
.gf-code-dark .ace_entity.ace_other,\
.gf-code-dark .ace_entity.ace_other.ace_attribute-name,\
.gf-code-dark .ace_variable {\
color: #a6e22e\
}\
.gf-code-dark .ace_variable.ace_parameter {\
font-style: italic;\
color: #fd971f\
}\
.gf-code-dark .ace_string {\
color: #74e680\
}\
.gf-code-dark .ace_paren {\
color: #f0a842\
}\
.gf-code-dark .ace_operator {\
color: #FFF\
}\
.gf-code-dark .ace_comment {\
color: #75715e\
}\
.gf-code-dark .ace_indent-guide {\
background: url(data:image/png;base64,ivborw0kggoaaaansuheugaaaaeaaaaccayaaaczgbynaaaaekleqvqimwpq0fd0zxbzd/wpaajvaoxesgneaaaaaelftksuqmcc) right repeat-y\
}";
var dom = require("../lib/dom");
dom.importCssString(exports.cssText, exports.cssClass);
});
/* jshint ignore:end */
...@@ -19,6 +19,7 @@ import "./directives/diff-view"; ...@@ -19,6 +19,7 @@ import "./directives/diff-view";
import './jquery_extended'; import './jquery_extended';
import './partials'; import './partials';
import './components/jsontree/jsontree'; import './components/jsontree/jsontree';
import './components/code_editor/code_editor';
import {grafanaAppDirective} from './components/grafana_app'; import {grafanaAppDirective} from './components/grafana_app';
import {sideMenuDirective} from './components/sidemenu/sidemenu'; import {sideMenuDirective} from './components/sidemenu/sidemenu';
......
...@@ -72,3 +72,8 @@ declare module 'd3' { ...@@ -72,3 +72,8 @@ declare module 'd3' {
var d3: any; var d3: any;
export default d3; export default d3;
} }
declare module 'ace' {
var ace: any;
export default ace;
}
<query-editor-row query-ctrl="ctrl" can-collapse="false"> <query-editor-row query-ctrl="ctrl" can-collapse="false">
<div class="gf-form-inline"> <div class="gf-form-inline">
<div class="gf-form gf-form--grow"> <div class="gf-form gf-form--grow">
<textarea rows="10" class="gf-form-input" ng-model="ctrl.target.rawSql" spellcheck="false" placeholder="query expression" data-min-length=0 data-items=100 ng-model-onblur ng-change="ctrl.panelCtrl.refresh()"></textarea> <code-editor content="ctrl.target.rawSql" on-change="ctrl.panelCtrl.refresh()" data-mode="sql">
</code-editor>
</div> </div>
</div> </div>
......
///<reference path="../../../headers/common.d.ts" />
import {PrometheusDatasource} from "./datasource";
export class PromCompleter {
identifierRegexps = [/[\[\]a-zA-Z_0-9=]/];
constructor(private datasource: PrometheusDatasource) {
}
getCompletions(editor, session, pos, prefix, callback) {
if (prefix === '[') {
var vectors = [];
for (let unit of ['s', 'm', 'h']) {
for (let value of [1,5,10,30]) {
vectors.push({caption: value+unit, value: '['+value+unit, meta: 'range vector'});
}
}
callback(null, vectors);
return;
}
var query = prefix;
var line = editor.session.getLine(pos.row);
return this.datasource.performSuggestQuery(query).then(metricNames => {
callback(null, metricNames.map(name => {
let value = name;
if (prefix === '(') {
value = '(' + name;
}
return {
caption: name,
value: value,
meta: 'metric',
};
}));
});
}
}
...@@ -11,8 +11,26 @@ import TableModel from 'app/core/table_model'; ...@@ -11,8 +11,26 @@ import TableModel from 'app/core/table_model';
var durationSplitRegexp = /(\d+)(ms|s|m|h|d|w|M|y)/; var durationSplitRegexp = /(\d+)(ms|s|m|h|d|w|M|y)/;
/** @ngInject */ function prometheusSpecialRegexEscape(value) {
export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateSrv, timeSrv) { return value.replace(/[\\^$*+?.()|[\]{}]/g, '\\\\$&');
}
export class PrometheusDatasource {
type: string;
editorSrc: string;
name: string;
supportMetrics: boolean;
url: string;
directUrl: string;
basicAuth: any;
withCredentials: any;
/** @ngInject */
constructor(instanceSettings,
private $q,
private backendSrv,
private templateSrv,
private timeSrv) {
this.type = 'prometheus'; this.type = 'prometheus';
this.editorSrc = 'app/features/prometheus/partials/query.editor.html'; this.editorSrc = 'app/features/prometheus/partials/query.editor.html';
this.name = instanceSettings.name; this.name = instanceSettings.name;
...@@ -21,8 +39,9 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS ...@@ -21,8 +39,9 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
this.directUrl = instanceSettings.directUrl; this.directUrl = instanceSettings.directUrl;
this.basicAuth = instanceSettings.basicAuth; this.basicAuth = instanceSettings.basicAuth;
this.withCredentials = instanceSettings.withCredentials; this.withCredentials = instanceSettings.withCredentials;
}
this._request = function(method, url, requestId) { _request(method, url, requestId?) {
var options: any = { var options: any = {
url: this.url + url, url: this.url + url,
method: method, method: method,
...@@ -32,20 +51,17 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS ...@@ -32,20 +51,17 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
if (this.basicAuth || this.withCredentials) { if (this.basicAuth || this.withCredentials) {
options.withCredentials = true; options.withCredentials = true;
} }
if (this.basicAuth) { if (this.basicAuth) {
options.headers = { options.headers = {
"Authorization": this.basicAuth "Authorization": this.basicAuth
}; };
} }
return backendSrv.datasourceRequest(options); return this.backendSrv.datasourceRequest(options);
};
function prometheusSpecialRegexEscape(value) {
return value.replace(/[\\^$*+?.()|[\]{}]/g, '\\\\$&');
} }
this.interpolateQueryExpr = function(value, variable, defaultFormatFn) { interpolateQueryExpr(value, variable, defaultFormatFn) {
// if no multi or include all do not regexEscape // if no multi or include all do not regexEscape
if (!variable.multi && !variable.includeAll) { if (!variable.multi && !variable.includeAll) {
return value; return value;
...@@ -57,14 +73,13 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS ...@@ -57,14 +73,13 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
var escapedValues = _.map(value, prometheusSpecialRegexEscape); var escapedValues = _.map(value, prometheusSpecialRegexEscape);
return escapedValues.join('|'); return escapedValues.join('|');
}; }
this.targetContainsTemplate = function(target) { targetContainsTemplate(target) {
return templateSrv.variableExists(target.expr); return this.templateSrv.variableExists(target.expr);
}; }
// Called once per panel (graph) query(options) {
this.query = function(options) {
var self = this; var self = this;
var start = this.getPrometheusTime(options.range.from, false); var start = this.getPrometheusTime(options.range.from, false);
var end = this.getPrometheusTime(options.range.to, true); var end = this.getPrometheusTime(options.range.to, true);
...@@ -82,10 +97,10 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS ...@@ -82,10 +97,10 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
activeTargets.push(target); activeTargets.push(target);
var query: any = {}; var query: any = {};
query.expr = templateSrv.replace(target.expr, options.scopedVars, self.interpolateQueryExpr); query.expr = this.templateSrv.replace(target.expr, options.scopedVars, self.interpolateQueryExpr);
query.requestId = options.panelId + target.refId; query.requestId = options.panelId + target.refId;
var interval = templateSrv.replace(target.interval, options.scopedVars) || options.interval; var interval = this.templateSrv.replace(target.interval, options.scopedVars) || options.interval;
var intervalFactor = target.intervalFactor || 1; var intervalFactor = target.intervalFactor || 1;
target.step = query.step = this.calculateInterval(interval, intervalFactor); target.step = query.step = this.calculateInterval(interval, intervalFactor);
var range = Math.ceil(end - start); var range = Math.ceil(end - start);
...@@ -95,14 +110,14 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS ...@@ -95,14 +110,14 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
// No valid targets, return the empty result to save a round trip. // No valid targets, return the empty result to save a round trip.
if (_.isEmpty(queries)) { if (_.isEmpty(queries)) {
return $q.when({ data: [] }); return this.$q.when({ data: [] });
} }
var allQueryPromise = _.map(queries, query => { var allQueryPromise = _.map(queries, query => {
return this.performTimeSeriesQuery(query, start, end); return this.performTimeSeriesQuery(query, start, end);
}); });
return $q.all(allQueryPromise).then(responseList => { return this.$q.all(allQueryPromise).then(responseList => {
var result = []; var result = [];
var index = 0; var index = 0;
...@@ -122,27 +137,27 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS ...@@ -122,27 +137,27 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
return { data: result }; return { data: result };
}); });
}; }
this.adjustStep = function(step, autoStep, range) { adjustStep(step, autoStep, range) {
// Prometheus drop query if range/step > 11000 // Prometheus drop query if range/step > 11000
// calibrate step if it is too big // calibrate step if it is too big
if (step !== 0 && range / step > 11000) { if (step !== 0 && range / step > 11000) {
step = Math.ceil(range / 11000); step = Math.ceil(range / 11000);
} }
return Math.max(step, autoStep); return Math.max(step, autoStep);
}; }
this.performTimeSeriesQuery = function(query, start, end) { performTimeSeriesQuery(query, start, end) {
if (start > end) { if (start > end) {
throw { message: 'Invalid time range' }; throw { message: 'Invalid time range' };
} }
var url = '/api/v1/query_range?query=' + encodeURIComponent(query.expr) + '&start=' + start + '&end=' + end + '&step=' + query.step; var url = '/api/v1/query_range?query=' + encodeURIComponent(query.expr) + '&start=' + start + '&end=' + end + '&step=' + query.step;
return this._request('GET', url, query.requestId); return this._request('GET', url, query.requestId);
}; }
this.performSuggestQuery = function(query) { performSuggestQuery(query) {
var url = '/api/v1/label/__name__/values'; var url = '/api/v1/label/__name__/values';
return this._request('GET', url).then(function(result) { return this._request('GET', url).then(function(result) {
...@@ -150,41 +165,30 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS ...@@ -150,41 +165,30 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
return metricName.indexOf(query) !== 1; return metricName.indexOf(query) !== 1;
}); });
}); });
};
this.metricFindQuery = function(query) {
if (!query) { return $q.when([]); }
var interpolated;
try {
interpolated = templateSrv.replace(query, {}, this.interpolateQueryExpr);
} catch (err) {
return $q.reject(err);
} }
var metricFindQuery = new PrometheusMetricFindQuery(this, interpolated, timeSrv); metricFindQuery(query) {
if (!query) { return this.$q.when([]); }
let interpolated = this.templateSrv.replace(query, {}, this.interpolateQueryExpr);
var metricFindQuery = new PrometheusMetricFindQuery(this, interpolated, this.timeSrv);
return metricFindQuery.process(); return metricFindQuery.process();
}; }
this.annotationQuery = function(options) { annotationQuery(options) {
var annotation = options.annotation; var annotation = options.annotation;
var expr = annotation.expr || ''; var expr = annotation.expr || '';
var tagKeys = annotation.tagKeys || ''; var tagKeys = annotation.tagKeys || '';
var titleFormat = annotation.titleFormat || ''; var titleFormat = annotation.titleFormat || '';
var textFormat = annotation.textFormat || ''; var textFormat = annotation.textFormat || '';
if (!expr) { return $q.when([]); } if (!expr) { return this.$q.when([]); }
var interpolated; var interpolated = this.templateSrv.replace(expr, {}, this.interpolateQueryExpr);
try {
interpolated = templateSrv.replace(expr, {}, this.interpolateQueryExpr);
} catch (err) {
return $q.reject(err);
}
var step = '60s'; var step = '60s';
if (annotation.step) { if (annotation.step) {
step = templateSrv.replace(annotation.step); step = this.templateSrv.replace(annotation.step);
} }
var start = this.getPrometheusTime(options.range.from, false); var start = this.getPrometheusTime(options.range.from, false);
...@@ -222,19 +226,19 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS ...@@ -222,19 +226,19 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
return eventList; return eventList;
}); });
}; }
this.testDatasource = function() { testDatasource() {
return this.metricFindQuery('metrics(.*)').then(function() { return this.metricFindQuery('metrics(.*)').then(function() {
return { status: 'success', message: 'Data source is working', title: 'Success' }; return { status: 'success', message: 'Data source is working', title: 'Success' };
}); });
}; }
this.calculateInterval = function(interval, intervalFactor) { calculateInterval(interval, intervalFactor) {
return Math.ceil(this.intervalSeconds(interval) * intervalFactor); return Math.ceil(this.intervalSeconds(interval) * intervalFactor);
}; }
this.intervalSeconds = function(interval) { intervalSeconds(interval) {
var m = interval.match(durationSplitRegexp); var m = interval.match(durationSplitRegexp);
var dur = moment.duration(parseInt(m[1]), m[2]); var dur = moment.duration(parseInt(m[1]), m[2]);
var sec = dur.asSeconds(); var sec = dur.asSeconds();
...@@ -243,9 +247,9 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS ...@@ -243,9 +247,9 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
} }
return sec; return sec;
}; }
this.transformMetricData = function(md, options, start, end) { transformMetricData(md, options, start, end) {
var dps = [], var dps = [],
metricLabel = null; metricLabel = null;
...@@ -273,9 +277,9 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS ...@@ -273,9 +277,9 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
} }
return { target: metricLabel, datapoints: dps }; return { target: metricLabel, datapoints: dps };
}; }
this.transformMetricDataToTable = function(md) { transformMetricDataToTable(md) {
var table = new TableModel(); var table = new TableModel();
var i, j; var i, j;
var metricLabels = {}; var metricLabels = {};
...@@ -325,17 +329,17 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS ...@@ -325,17 +329,17 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
}); });
return table; return table;
}; }
this.createMetricLabel = function(labelData, options) { createMetricLabel(labelData, options) {
if (_.isUndefined(options) || _.isEmpty(options.legendFormat)) { if (_.isUndefined(options) || _.isEmpty(options.legendFormat)) {
return this.getOriginalMetricName(labelData); return this.getOriginalMetricName(labelData);
} }
return this.renderTemplate(templateSrv.replace(options.legendFormat), labelData) || '{}'; return this.renderTemplate(this.templateSrv.replace(options.legendFormat), labelData) || '{}';
}; }
this.renderTemplate = function(aliasPattern, aliasData) { renderTemplate(aliasPattern, aliasData) {
var aliasRegex = /\{\{\s*(.+?)\s*\}\}/g; var aliasRegex = /\{\{\s*(.+?)\s*\}\}/g;
return aliasPattern.replace(aliasRegex, function(match, g1) { return aliasPattern.replace(aliasRegex, function(match, g1) {
if (aliasData[g1]) { if (aliasData[g1]) {
...@@ -343,21 +347,21 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS ...@@ -343,21 +347,21 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
} }
return g1; return g1;
}); });
}; }
this.getOriginalMetricName = function(labelData) { getOriginalMetricName(labelData) {
var metricName = labelData.__name__ || ''; var metricName = labelData.__name__ || '';
delete labelData.__name__; delete labelData.__name__;
var labelPart = _.map(_.toPairs(labelData), function(label) { var labelPart = _.map(_.toPairs(labelData), function(label) {
return label[0] + '="' + label[1] + '"'; return label[0] + '="' + label[1] + '"';
}).join(','); }).join(',');
return metricName + '{' + labelPart + '}'; return metricName + '{' + labelPart + '}';
}; }
this.getPrometheusTime = function(date, roundUp) { getPrometheusTime(date, roundUp) {
if (_.isString(date)) { if (_.isString(date)) {
date = dateMath.parse(date, roundUp); date = dateMath.parse(date, roundUp);
} }
return Math.ceil(date.valueOf() / 1000); return Math.ceil(date.valueOf() / 1000);
}; }
} }
<query-editor-row query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="false"> <query-editor-row query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="false">
<div class="gf-form-inline"> <div class="gf-form-inline">
<div class="gf-form gf-form--grow"> <div class="gf-form gf-form--grow">
<textarea rows="3" class="gf-form-input" ng-model="ctrl.target.expr" spellcheck="false" placeholder="query expression" data-min-length=0 data-items=100 give-focus="ctrl.target.refId == 'A'" ng-model-onblur ng-change="ctrl.refreshMetricData()"></textarea> <code-editor content="ctrl.target.expr" on-change="ctrl.refreshMetricData()"
get-completer="ctrl.getCompleter()" data-mode="prometheus">
</code-editor>
</div> </div>
</div> </div>
...@@ -38,17 +40,6 @@ ...@@ -38,17 +40,6 @@
</div> </div>
</div> </div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form max-width-26">
<label class="gf-form-label width-8">Metric lookup</label>
<input type="text" class="gf-form-input" ng-model="ctrl.target.metric" spellcheck='false' bs-typeahead="ctrl.suggestMetrics" placeholder="metric name" data-min-length=0 data-items=100>
</div>
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label width-6">Format as</label> <label class="gf-form-label width-6">Format as</label>
<div class="gf-form-select-wrapper width-8"> <div class="gf-form-select-wrapper width-8">
...@@ -66,4 +57,5 @@ ...@@ -66,4 +57,5 @@
</div> </div>
</div> </div>
</query-editor-row> </query-editor-row>
...@@ -6,6 +6,7 @@ import moment from 'moment'; ...@@ -6,6 +6,7 @@ import moment from 'moment';
import * as dateMath from 'app/core/utils/datemath'; import * as dateMath from 'app/core/utils/datemath';
import {QueryCtrl} from 'app/plugins/sdk'; import {QueryCtrl} from 'app/plugins/sdk';
import {PromCompleter} from './completer';
class PrometheusQueryCtrl extends QueryCtrl { class PrometheusQueryCtrl extends QueryCtrl {
static templateUrl = 'partials/query.editor.html'; static templateUrl = 'partials/query.editor.html';
...@@ -15,6 +16,7 @@ class PrometheusQueryCtrl extends QueryCtrl { ...@@ -15,6 +16,7 @@ class PrometheusQueryCtrl extends QueryCtrl {
formats: any; formats: any;
oldTarget: any; oldTarget: any;
suggestMetrics: any; suggestMetrics: any;
getMetricsAutocomplete: any;
linkToPrometheus: any; linkToPrometheus: any;
/** @ngInject */ /** @ngInject */
...@@ -36,24 +38,19 @@ class PrometheusQueryCtrl extends QueryCtrl { ...@@ -36,24 +38,19 @@ class PrometheusQueryCtrl extends QueryCtrl {
{text: 'Table', value: 'table'}, {text: 'Table', value: 'table'},
]; ];
$scope.$on('typeahead-updated', () => {
this.$scope.$apply(() => {
this.target.expr += this.target.metric;
this.metric = '';
this.refreshMetricData();
});
});
// called from typeahead so need this
// here in order to ensure this ref
this.suggestMetrics = (query, callback) => {
console.log(this);
this.datasource.performSuggestQuery(query).then(callback);
};
this.updateLink(); this.updateLink();
} }
getCompleter(query) {
return new PromCompleter(this.datasource);
// console.log('getquery);
// return this.datasource.performSuggestQuery(query).then(res => {
// return res.map(item => {
// return {word: item, type: 'metric'};
// });
// });
}
getDefaultFormat() { getDefaultFormat() {
if (this.panelCtrl.panel.type === 'table') { if (this.panelCtrl.panel.type === 'table') {
return 'table'; return 'table';
......
...@@ -33,6 +33,7 @@ System.config({ ...@@ -33,6 +33,7 @@ System.config({
"jquery.flot.gauge": "vendor/flot/jquery.flot.gauge", "jquery.flot.gauge": "vendor/flot/jquery.flot.gauge",
"d3": "vendor/d3/d3.js", "d3": "vendor/d3/d3.js",
"jquery.flot.dashes": "vendor/flot/jquery.flot.dashes", "jquery.flot.dashes": "vendor/flot/jquery.flot.dashes",
"ace": "vendor/npm/ace-builds/src-noconflict/ace"
}, },
packages: { packages: {
...@@ -73,5 +74,9 @@ System.config({ ...@@ -73,5 +74,9 @@ System.config({
format: 'global', format: 'global',
exports: 'Mousetrap' exports: 'Mousetrap'
}, },
'vendor/npm/ace-builds/src-noconflict/ace.js': {
format: 'global',
exports: 'ace'
}
} }
}); });
...@@ -77,6 +77,7 @@ ...@@ -77,6 +77,7 @@
@import "components/row.scss"; @import "components/row.scss";
@import "components/json_explorer.scss"; @import "components/json_explorer.scss";
@import "components/collapse_box.scss"; @import "components/collapse_box.scss";
@import "components/code_editor.scss";
// PAGES // PAGES
@import "pages/login"; @import "pages/login";
......
.gf-code-editor {
min-height: 2.60rem;
min-width: 20rem;
flex-grow: 1;
margin-right: 0.25rem;
visibility: hidden;
&.ace_editor {
@include font-family-monospace();
font-size: 1rem;
min-height: 2.60rem;
@include border-radius($input-border-radius-sm);
border: $input-btn-border-width solid $input-border-color;
}
&--theme-loaded {
visibility: visible;
}
}
.ace_editor.ace_autocomplete {
@include font-family-monospace();
font-size: 1rem;
// Ace editor adds <style> tag at the end of <head>, after grafana.css, so !important
// is used for overriding styles with the same CSS specificity.
background-color: $dropdownBackground !important;
color: $dropdownLinkColor !important;
border: 1px solid $dropdownBorder !important;
width: 320px !important;
.ace_scroller {
.ace_selected, .ace_active-line, .ace_line-hover {
color: $dropdownLinkColorHover;
background-color: $dropdownLinkBackgroundHover !important;
}
.ace_line-hover {
border-color: transparent;
}
.ace_completion-highlight {
color: $yellow;
}
.ace_rightAlignedText {
color: $text-muted;
z-index: 0;
}
}
}
$doc-font-size: $font-size-sm;
.ace_tooltip.ace_doc-tooltip {
@include font-family-monospace();
font-size: $doc-font-size;
background-color: $popover-help-bg;
color: $popover-help-color;
background-image: none;
border: 1px solid $dropdownBorder;
padding: 0.5rem 1rem;
hr {
background-color: $popover-help-color;
margin: 0.5rem 0rem;
}
code {
padding: 0px 1px;
margin: 0px;
}
}
.ace_tooltip {
border-radius: 3px;
}
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
"jquery.flot.gauge": "vendor/flot/jquery.flot.gauge", "jquery.flot.gauge": "vendor/flot/jquery.flot.gauge",
"d3": "vendor/d3/d3.js", "d3": "vendor/d3/d3.js",
"jquery.flot.dashes": "vendor/flot/jquery.flot.dashes", "jquery.flot.dashes": "vendor/flot/jquery.flot.dashes",
"ace": "vendor/npm/ace-builds/src-noconflict/ace",
}, },
packages: { packages: {
...@@ -73,6 +74,10 @@ ...@@ -73,6 +74,10 @@
format: 'global', format: 'global',
exports: 'Mousetrap' exports: 'Mousetrap'
}, },
'vendor/npm/ace-builds/src-noconflict/ace.js': {
format: 'global',
exports: 'ace'
},
} }
}); });
......
...@@ -19,6 +19,7 @@ module.exports = function(config) { ...@@ -19,6 +19,7 @@ module.exports = function(config) {
cwd: './node_modules', cwd: './node_modules',
expand: true, expand: true,
src: [ src: [
'ace-builds/src-noconflict/**/*',
'eventemitter3/*.js', 'eventemitter3/*.js',
'systemjs/dist/*.js', 'systemjs/dist/*.js',
'es6-promise/**/*', 'es6-promise/**/*',
......
...@@ -21,7 +21,10 @@ module.exports = function(config, grunt) { ...@@ -21,7 +21,10 @@ module.exports = function(config, grunt) {
return; return;
} }
gaze(config.srcDir + '/**/*', function(err, watcher) { gaze([
config.srcDir + '/app/**/*',
config.srcDir + '/sass/**/*',
], function(err, watcher) {
console.log('Gaze watchers setup'); console.log('Gaze watchers setup');
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment