Merge branch 'master' into ace-editor

This commit is contained in:
Alexander Zobnin
2017-08-24 12:37:18 +03:00
18 changed files with 449 additions and 123 deletions

View File

@@ -122,7 +122,7 @@ function (angular, _, coreModule) {
vm.selectValue = function(option, event, commitChange, excludeOthers) {
if (!option) { return; }
option.selected = !option.selected;
option.selected = vm.variable.multi ? !option.selected: true;
commitChange = commitChange || false;
excludeOthers = excludeOthers || false;

View File

@@ -29,10 +29,6 @@ export class SubmenuCtrl {
var search = _.extend(this.$location.search(), {editview: editview});
this.$location.search(search);
}
exitBuildMode() {
this.dashboard.toggleEditMode();
}
}
export function submenuDirective() {

View File

@@ -106,7 +106,7 @@ export class VariableEditorCtrl {
var clone = _.cloneDeep(variable.getSaveModel());
$scope.current = variableSrv.createVariableFromModel(clone);
$scope.current.name = 'copy_of_'+variable.name;
$scope.variableSrv.addVariable($scope.current);
variableSrv.addVariable($scope.current);
};
$scope.update = function() {

View File

@@ -128,7 +128,7 @@ export class QueryVariable implements Variable {
}
metricFindQuery(datasource, query) {
var options = {range: undefined};
var options = {range: undefined, variable: this};
if (this.refresh === 2) {
options.range = this.timeSrv.timeRange();

View File

@@ -51,6 +51,31 @@ describe('templateSrv', function() {
});
});
describe('getAdhocFilters', function() {
beforeEach(function() {
initTemplateSrv([
{type: 'datasource', name: 'ds', current: {value: 'logstash', text: 'logstash'}},
{type: 'adhoc', name: 'test', datasource: 'oogle', filters: [1]},
{type: 'adhoc', name: 'test2', datasource: '$ds', filters: [2]},
]);
});
it('should return filters if datasourceName match', function() {
var filters = _templateSrv.getAdhocFilters('oogle');
expect(filters).to.eql([1]);
});
it('should return empty array if datasourceName does not match', function() {
var filters = _templateSrv.getAdhocFilters('oogleasdasd');
expect(filters).to.eql([]);
});
it('should return filters when datasourceName match via data source variable', function() {
var filters = _templateSrv.getAdhocFilters('logstash');
expect(filters).to.eql([2]);
});
});
describe('replace can pass multi / all format', function() {
beforeEach(function() {
initTemplateSrv([{type: 'query', name: 'test', current: {value: ['value1', 'value2'] }}]);

View File

@@ -15,7 +15,6 @@ function (angular, _, kbn) {
this._index = {};
this._texts = {};
this._grafanaVariables = {};
this._adhocVariables = {};
// default built ins
this._builtIns = {};
@@ -30,24 +29,16 @@ function (angular, _, kbn) {
this.updateTemplateData = function() {
this._index = {};
this._filters = {};
this._adhocVariables = {};
for (var i = 0; i < this.variables.length; i++) {
var variable = this.variables[i];
// add adhoc filters to it's own index
if (variable.type === 'adhoc') {
this._adhocVariables[variable.datasource] = variable;
continue;
}
if (!variable.current || !variable.current.isNone && !variable.current.value) {
continue;
}
this._index[variable.name] = variable;
}
};
this.variableInitialized = function(variable) {
@@ -55,11 +46,26 @@ function (angular, _, kbn) {
};
this.getAdhocFilters = function(datasourceName) {
var variable = this._adhocVariables[datasourceName];
if (variable) {
return variable.filters || [];
var filters = [];
for (var i = 0; i < this.variables.length; i++) {
var variable = this.variables[i];
if (variable.type !== 'adhoc') {
continue;
}
if (variable.datasource === datasourceName) {
filters = filters.concat(variable.filters);
}
if (variable.datasource.indexOf('$') === 0) {
if (this.replace(variable.datasource) === datasourceName) {
filters = filters.concat(variable.filters);
}
}
}
return [];
return filters;
};
function luceneEscape(value) {

View File

@@ -247,8 +247,6 @@ export class VariableSrv {
}
filter.operator = options.operator;
variable.setFilters(filters);
this.variableUpdated(variable, true);
}

View File

@@ -1,24 +1,27 @@
///<reference path="../../../headers/common.d.ts" />
import _ from 'lodash';
import ResponseParser from './response_parser';
export class MysqlDatasource {
id: any;
name: any;
responseParser: ResponseParser;
/** @ngInject **/
constructor(instanceSettings, private backendSrv, private $q, private templateSrv) {
this.name = instanceSettings.name;
this.id = instanceSettings.id;
this.responseParser = new ResponseParser(this.$q);
}
interpolateVariable(value) {
if (typeof value === 'string') {
return '\"' + value + '\"';
return '\'' + value + '\'';
}
var quotedValues = _.map(value, function(val) {
return '\"' + val + '\"';
return '\'' + val + '\'';
});
return quotedValues.join(',');
}
@@ -49,7 +52,7 @@ export class MysqlDatasource {
to: options.range.to.valueOf().toString(),
queries: queries,
}
}).then(this.processQueryResult.bind(this));
}).then(this.responseParser.processQueryResult);
}
annotationQuery(options) {
@@ -72,46 +75,30 @@ export class MysqlDatasource {
to: options.range.to.valueOf().toString(),
queries: [query],
}
}).then(this.transformAnnotationResponse.bind(this, options));
}).then(data => this.responseParser.transformAnnotationResponse(options, data));
}
transformAnnotationResponse(options, data) {
const table = data.data.results[options.annotation.name].tables[0];
metricFindQuery(query, optionalOptions) {
let refId = 'tempvar';
if (optionalOptions && optionalOptions.variable && optionalOptions.variable.name) {
refId = optionalOptions.variable.name;
}
let timeColumnIndex = -1;
let titleColumnIndex = -1;
let textColumnIndex = -1;
let tagsColumnIndex = -1;
const interpolatedQuery = {
refId: refId,
datasourceId: this.id,
rawSql: this.templateSrv.replace(query, {}, this.interpolateVariable),
format: 'table',
};
for (let i = 0; i < table.columns.length; i++) {
if (table.columns[i].text === 'time_sec') {
timeColumnIndex = i;
} else if (table.columns[i].text === 'title') {
titleColumnIndex = i;
} else if (table.columns[i].text === 'text') {
textColumnIndex = i;
} else if (table.columns[i].text === 'tags') {
tagsColumnIndex = i;
return this.backendSrv.datasourceRequest({
url: '/api/tsdb/query',
method: 'POST',
data: {
queries: [interpolatedQuery],
}
}
if (timeColumnIndex === -1) {
return this.$q.reject({message: 'Missing mandatory time column (with time_sec column alias) in annotation query.'});
}
const list = [];
for (let i = 0; i < table.rows.length; i++) {
const row = table.rows[i];
list.push({
annotation: options.annotation,
time: Math.floor(row[timeColumnIndex]) * 1000,
title: row[titleColumnIndex],
text: row[textColumnIndex],
tags: row[tagsColumnIndex] ? row[tagsColumnIndex].trim().split(/\s*,\s*/) : []
});
}
return list;
})
.then(data => this.responseParser.parseMetricFindQueryResult(refId, data));
}
testDatasource() {
@@ -141,39 +128,5 @@ export class MysqlDatasource {
}
});
}
processQueryResult(res) {
var data = [];
if (!res.data.results) {
return {data: data};
}
for (let key in res.data.results) {
let queryRes = res.data.results[key];
if (queryRes.series) {
for (let series of queryRes.series) {
data.push({
target: series.name,
datapoints: series.points,
refId: queryRes.refId,
meta: queryRes.meta,
});
}
}
if (queryRes.tables) {
for (let table of queryRes.tables) {
table.type = 'table';
table.refId = queryRes.refId;
table.meta = queryRes.meta;
data.push(table);
}
}
}
return {data: data};
}
}

View File

@@ -14,7 +14,6 @@
}
},
"state": "alpha",
"alerting": true,
"annotations": true,
"metrics": true

View File

@@ -0,0 +1,143 @@
///<reference path="../../../headers/common.d.ts" />
import _ from 'lodash';
export default class ResponseParser {
constructor(private $q){}
processQueryResult(res) {
var data = [];
if (!res.data.results) {
return {data: data};
}
for (let key in res.data.results) {
let queryRes = res.data.results[key];
if (queryRes.series) {
for (let series of queryRes.series) {
data.push({
target: series.name,
datapoints: series.points,
refId: queryRes.refId,
meta: queryRes.meta,
});
}
}
if (queryRes.tables) {
for (let table of queryRes.tables) {
table.type = 'table';
table.refId = queryRes.refId;
table.meta = queryRes.meta;
data.push(table);
}
}
}
return {data: data};
}
parseMetricFindQueryResult(refId, results) {
if (!results || results.data.length === 0 || results.data.results[refId].meta.rowCount === 0) { return []; }
const columns = results.data.results[refId].tables[0].columns;
const rows = results.data.results[refId].tables[0].rows;
const textColIndex = this.findColIndex(columns, '__text');
const valueColIndex = this.findColIndex(columns, '__value');
if (columns.length === 2 && textColIndex !== -1 && valueColIndex !== -1){
return this.transformToKeyValueList(rows, textColIndex, valueColIndex);
}
return this.transformToSimpleList(rows);
}
transformToKeyValueList(rows, textColIndex, valueColIndex) {
const res = [];
for (let i = 0; i < rows.length; i++) {
if (!this.containsKey(res, rows[i][textColIndex])) {
res.push({text: rows[i][textColIndex], value: rows[i][valueColIndex]});
}
}
return res;
}
transformToSimpleList(rows) {
const res = [];
for (let i = 0; i < rows.length; i++) {
for (let j = 0; j < rows[i].length; j++) {
const value = rows[i][j];
if ( res.indexOf( value ) === -1 ) {
res.push(value);
}
}
}
return _.map(res, value => {
return { text: value};
});
}
findColIndex(columns, colName) {
for (let i = 0; i < columns.length; i++) {
if (columns[i].text === colName) {
return i;
}
}
return -1;
}
containsKey(res, key) {
for (let i = 0; i < res.length; i++) {
if (res[i].text === key) {
return true;
}
}
return false;
}
transformAnnotationResponse(options, data) {
const table = data.data.results[options.annotation.name].tables[0];
let timeColumnIndex = -1;
let titleColumnIndex = -1;
let textColumnIndex = -1;
let tagsColumnIndex = -1;
for (let i = 0; i < table.columns.length; i++) {
if (table.columns[i].text === 'time_sec') {
timeColumnIndex = i;
} else if (table.columns[i].text === 'title') {
titleColumnIndex = i;
} else if (table.columns[i].text === 'text') {
textColumnIndex = i;
} else if (table.columns[i].text === 'tags') {
tagsColumnIndex = i;
}
}
if (timeColumnIndex === -1) {
return this.$q.reject({message: 'Missing mandatory time column (with time_sec column alias) in annotation query.'});
}
const list = [];
for (let i = 0; i < table.rows.length; i++) {
const row = table.rows[i];
list.push({
annotation: options.annotation,
time: Math.floor(row[timeColumnIndex]) * 1000,
title: row[titleColumnIndex],
text: row[textColumnIndex],
tags: row[tagsColumnIndex] ? row[tagsColumnIndex].trim().split(/\s*,\s*/) : []
});
}
return list;
}
}

View File

@@ -76,4 +76,122 @@ describe('MySQLDatasource', function() {
});
});
describe('When performing metricFindQuery', function() {
let results;
const query = 'select * from atable';
const response = {
results: {
tempvar: {
meta: {
rowCount: 3
},
refId: 'tempvar',
tables: [
{
columns: [{text: 'title'}, {text: 'text'}],
rows: [
['aTitle', 'some text'],
['aTitle2', 'some text2'],
['aTitle3', 'some text3']
]
}
]
}
}
};
beforeEach(function() {
ctx.backendSrv.datasourceRequest = function(options) {
return ctx.$q.when({data: response, status: 200});
};
ctx.ds.metricFindQuery(query).then(function(data) { results = data; });
ctx.$rootScope.$apply();
});
it('should return list of all column values', function() {
expect(results.length).to.be(6);
expect(results[0].text).to.be('aTitle');
expect(results[5].text).to.be('some text3');
});
});
describe('When performing metricFindQuery with key, value columns', function() {
let results;
const query = 'select * from atable';
const response = {
results: {
tempvar: {
meta: {
rowCount: 3
},
refId: 'tempvar',
tables: [
{
columns: [{text: '__value'}, {text: '__text'}],
rows: [
['value1', 'aTitle'],
['value2', 'aTitle2'],
['value3', 'aTitle3']
]
}
]
}
}
};
beforeEach(function() {
ctx.backendSrv.datasourceRequest = function(options) {
return ctx.$q.when({data: response, status: 200});
};
ctx.ds.metricFindQuery(query).then(function(data) { results = data; });
ctx.$rootScope.$apply();
});
it('should return list of as text, value', function() {
expect(results.length).to.be(3);
expect(results[0].text).to.be('aTitle');
expect(results[0].value).to.be('value1');
expect(results[2].text).to.be('aTitle3');
expect(results[2].value).to.be('value3');
});
});
describe('When performing metricFindQuery with key, value columns and with duplicate keys', function() {
let results;
const query = 'select * from atable';
const response = {
results: {
tempvar: {
meta: {
rowCount: 3
},
refId: 'tempvar',
tables: [
{
columns: [{text: '__text'}, {text: '__value'}],
rows: [
['aTitle', 'same'],
['aTitle', 'same'],
['aTitle', 'diff']
]
}
]
}
}
};
beforeEach(function() {
ctx.backendSrv.datasourceRequest = function(options) {
return ctx.$q.when({data: response, status: 200});
};
ctx.ds.metricFindQuery(query).then(function(data) { results = data; });
ctx.$rootScope.$apply();
});
it('should return list of unique keys', function() {
expect(results.length).to.be(1);
expect(results[0].text).to.be('aTitle');
expect(results[0].value).to.be('same');
});
});
});

View File

@@ -1,4 +1,4 @@
<query-editor-row query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="true">
<query-editor-row query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="false">
<div class="gf-form-inline">
<div class="gf-form gf-form--grow">
<code-editor content="ctrl.target.expr" on-change="ctrl.refreshMetricData()"

View File

@@ -157,9 +157,9 @@ export class TableRenderer {
// because of the fixed table headers css only solution
// there is an issue if header cell is wider the cell
// this hack adds header content to cell (not visible)
var widthHack = '';
var columnHtml = '';
if (addWidthHack) {
widthHack = '<div class="table-panel-width-hack">' + this.table.columns[columnIndex].title + '</div>';
columnHtml = '<div class="table-panel-width-hack">' + this.table.columns[columnIndex].title + '</div>';
}
if (value === undefined) {
@@ -173,8 +173,6 @@ export class TableRenderer {
cellClasses.push("table-panel-cell-pre");
}
var columnHtml = widthHack + value;
if (column.style && column.style.link) {
// Render cell as link
var scopedVars = this.renderRowVariables(rowIndex);
@@ -185,11 +183,13 @@ export class TableRenderer {
var cellTarget = column.style.linkTargetBlank ? '_blank' : '';
cellClasses.push("table-panel-cell-link");
columnHtml = `
columnHtml += `
<a href="${cellLink}" target="${cellTarget}" data-link-tooltip data-original-title="${cellLinkTooltip}" data-placement="right">
${columnHtml}
${value}
</a>
`;
} else {
columnHtml += value;
}
if (column.filterable) {

View File

@@ -84,7 +84,7 @@
a {
padding: 0.45em 0 0.45em 1.1em;
height: 100%;
width: 100%;
display: inline-block;
}
}