mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge remote-tracking branch 'upstream/master' into postgres-query-builder
This commit is contained in:
commit
1d711924bc
@ -88,6 +88,9 @@ jobs:
|
||||
- run:
|
||||
name: run linters
|
||||
command: 'gometalinter.v2 --enable-gc --vendor --deadline 10m --disable-all --enable=deadcode --enable=ineffassign --enable=structcheck --enable=unconvert --enable=varcheck ./...'
|
||||
- run:
|
||||
name: run go vet
|
||||
command: 'go vet ./pkg/...'
|
||||
|
||||
test-frontend:
|
||||
docker:
|
||||
|
@ -15,6 +15,7 @@
|
||||
* **Postgres/MySQL/MSSQL**: Use floor rounding in $__timeGroup macro function [#12460](https://github.com/grafana/grafana/issues/12460), thx [@svenklemm](https://github.com/svenklemm)
|
||||
* **MySQL/MSSQL**: Use datetime format instead of epoch for $__timeFilter, $__timeFrom and $__timeTo macros [#11618](https://github.com/grafana/grafana/issues/11618) [#11619](https://github.com/grafana/grafana/issues/11619), thx [@AustinWinstanley](https://github.com/AustinWinstanley)
|
||||
* **Github OAuth**: Allow changes of user info at Github to be synched to Grafana when signing in [#11818](https://github.com/grafana/grafana/issues/11818), thx [@rwaweber](https://github.com/rwaweber)
|
||||
* **Alerting**: Fix diff and percent_diff reducers [#11563](https://github.com/grafana/grafana/issues/11563), thx [@jessetane](https://github.com/jessetane)
|
||||
|
||||
# 5.2.2 (unreleased)
|
||||
|
||||
|
@ -149,17 +149,17 @@
|
||||
"classnames": "^2.2.5",
|
||||
"clipboard": "^1.7.1",
|
||||
"d3": "^4.11.0",
|
||||
"d3-scale-chromatic": "^1.1.1",
|
||||
"d3-scale-chromatic": "^1.3.0",
|
||||
"eventemitter3": "^2.0.3",
|
||||
"file-saver": "^1.3.3",
|
||||
"immutable": "^3.8.2",
|
||||
"jquery": "^3.2.1",
|
||||
"lodash": "^4.17.4",
|
||||
"lodash": "^4.17.10",
|
||||
"mini-css-extract-plugin": "^0.4.0",
|
||||
"mobx": "^3.4.1",
|
||||
"mobx-react": "^4.3.5",
|
||||
"mobx-state-tree": "^1.3.1",
|
||||
"moment": "^2.18.1",
|
||||
"moment": "^2.22.2",
|
||||
"mousetrap": "^1.6.0",
|
||||
"mousetrap-global-bind": "^1.1.0",
|
||||
"optimize-css-assets-webpack-plugin": "^4.0.2",
|
||||
|
@ -108,9 +108,9 @@ func (s *SimpleReducer) Reduce(series *tsdb.TimeSeries) null.Float {
|
||||
break
|
||||
}
|
||||
}
|
||||
// get other points
|
||||
// get the oldest point
|
||||
points = points[0:i]
|
||||
for i := len(points) - 1; i >= 0; i-- {
|
||||
for i := 0; i < len(points); i++ {
|
||||
if points[i][0].Valid {
|
||||
allNull = false
|
||||
value = first - points[i][0].Float64
|
||||
@ -131,9 +131,9 @@ func (s *SimpleReducer) Reduce(series *tsdb.TimeSeries) null.Float {
|
||||
break
|
||||
}
|
||||
}
|
||||
// get other points
|
||||
// get the oldest point
|
||||
points = points[0:i]
|
||||
for i := len(points) - 1; i >= 0; i-- {
|
||||
for i := 0; i < len(points); i++ {
|
||||
if points[i][0].Valid {
|
||||
allNull = false
|
||||
val := (first - points[i][0].Float64) / points[i][0].Float64 * 100
|
||||
|
@ -110,16 +110,35 @@ func TestSimpleReducer(t *testing.T) {
|
||||
So(reducer.Reduce(series).Float64, ShouldEqual, float64(3))
|
||||
})
|
||||
|
||||
Convey("diff", func() {
|
||||
Convey("diff one point", func() {
|
||||
result := testReducer("diff", 30)
|
||||
So(result, ShouldEqual, float64(0))
|
||||
})
|
||||
|
||||
Convey("diff two points", func() {
|
||||
result := testReducer("diff", 30, 40)
|
||||
So(result, ShouldEqual, float64(10))
|
||||
})
|
||||
|
||||
Convey("percent_diff", func() {
|
||||
Convey("diff three points", func() {
|
||||
result := testReducer("diff", 30, 40, 40)
|
||||
So(result, ShouldEqual, float64(10))
|
||||
})
|
||||
|
||||
Convey("percent_diff one point", func() {
|
||||
result := testReducer("percent_diff", 40)
|
||||
So(result, ShouldEqual, float64(0))
|
||||
})
|
||||
|
||||
Convey("percent_diff two points", func() {
|
||||
result := testReducer("percent_diff", 30, 40)
|
||||
So(result, ShouldEqual, float64(33.33333333333333))
|
||||
})
|
||||
|
||||
Convey("percent_diff three points", func() {
|
||||
result := testReducer("percent_diff", 30, 40, 40)
|
||||
So(result, ShouldEqual, float64(33.33333333333333))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -98,8 +98,6 @@ func (ns *NotificationService) Run(ctx context.Context) error {
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *NotificationService) SendWebhookSync(ctx context.Context, cmd *m.SendWebhookSync) error {
|
||||
|
@ -58,7 +58,9 @@ func (rs *RenderingService) renderViaPhantomJS(ctx context.Context, opts Opts) (
|
||||
cmdArgs = append([]string{fmt.Sprintf("--output-encoding=%s", opts.Encoding)}, cmdArgs...)
|
||||
}
|
||||
|
||||
commandCtx, _ := context.WithTimeout(ctx, opts.Timeout+time.Second*2)
|
||||
commandCtx, cancel := context.WithTimeout(ctx, opts.Timeout+time.Second*2)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(commandCtx, binPath, cmdArgs...)
|
||||
cmd.Stderr = cmd.Stdout
|
||||
|
||||
|
@ -218,7 +218,7 @@ func (c *baseClientImpl) ExecuteMultisearch(r *MultiSearchRequest) (*MultiSearch
|
||||
elapsed := time.Now().Sub(start)
|
||||
clientLog.Debug("Decoded multisearch json response", "took", elapsed)
|
||||
|
||||
msr.status = res.StatusCode
|
||||
msr.Status = res.StatusCode
|
||||
|
||||
return &msr, nil
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ type MultiSearchRequest struct {
|
||||
|
||||
// MultiSearchResponse represents a multi search response
|
||||
type MultiSearchResponse struct {
|
||||
status int `json:"status,omitempty"`
|
||||
Status int `json:"status,omitempty"`
|
||||
Responses []*SearchResponse `json:"responses"`
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ import { getNextCharacter, getPreviousCousin } from './utils/dom';
|
||||
import BracesPlugin from './slate-plugins/braces';
|
||||
import ClearPlugin from './slate-plugins/clear';
|
||||
import NewlinePlugin from './slate-plugins/newline';
|
||||
import PluginPrism, { configurePrismMetricsTokens } from './slate-plugins/prism/index';
|
||||
import PluginPrism, { setPrismTokens } from './slate-plugins/prism/index';
|
||||
import RunnerPlugin from './slate-plugins/runner';
|
||||
import debounce from './utils/debounce';
|
||||
import { processLabels, RATE_RANGES, cleanText } from './utils/prometheus';
|
||||
@ -17,13 +17,13 @@ import { processLabels, RATE_RANGES, cleanText } from './utils/prometheus';
|
||||
import Typeahead from './Typeahead';
|
||||
|
||||
const EMPTY_METRIC = '';
|
||||
const TYPEAHEAD_DEBOUNCE = 300;
|
||||
export const TYPEAHEAD_DEBOUNCE = 300;
|
||||
|
||||
function flattenSuggestions(s) {
|
||||
return s ? s.reduce((acc, g) => acc.concat(g.items), []) : [];
|
||||
}
|
||||
|
||||
const getInitialValue = query =>
|
||||
export const getInitialValue = query =>
|
||||
Value.fromJSON({
|
||||
document: {
|
||||
nodes: [
|
||||
@ -45,12 +45,14 @@ const getInitialValue = query =>
|
||||
},
|
||||
});
|
||||
|
||||
class Portal extends React.Component {
|
||||
class Portal extends React.Component<any, any> {
|
||||
node: any;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { index = 0, prefix = 'query' } = props;
|
||||
this.node = document.createElement('div');
|
||||
this.node.classList.add('explore-typeahead', `explore-typeahead-${props.index}`);
|
||||
this.node.classList.add(`slate-typeahead`, `slate-typeahead-${prefix}-${index}`);
|
||||
document.body.appendChild(this.node);
|
||||
}
|
||||
|
||||
@ -71,12 +73,14 @@ class QueryField extends React.Component<any, any> {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
const { prismDefinition = {}, prismLanguage = 'promql' } = props;
|
||||
|
||||
this.plugins = [
|
||||
BracesPlugin(),
|
||||
ClearPlugin(),
|
||||
RunnerPlugin({ handler: props.onPressEnter }),
|
||||
NewlinePlugin(),
|
||||
PluginPrism(),
|
||||
PluginPrism({ definition: prismDefinition, language: prismLanguage }),
|
||||
];
|
||||
|
||||
this.state = {
|
||||
@ -131,7 +135,8 @@ class QueryField extends React.Component<any, any> {
|
||||
if (!this.state.metrics) {
|
||||
return;
|
||||
}
|
||||
configurePrismMetricsTokens(this.state.metrics);
|
||||
setPrismTokens(this.props.prismLanguage, 'metrics', this.state.metrics);
|
||||
|
||||
// Trigger re-render
|
||||
window.requestAnimationFrame(() => {
|
||||
// Bogus edit to trigger highlighting
|
||||
@ -162,7 +167,7 @@ class QueryField extends React.Component<any, any> {
|
||||
const selection = window.getSelection();
|
||||
if (selection.anchorNode) {
|
||||
const wrapperNode = selection.anchorNode.parentElement;
|
||||
const editorNode = wrapperNode.closest('.query-field');
|
||||
const editorNode = wrapperNode.closest('.slate-query-field');
|
||||
if (!editorNode || this.state.value.isBlurred) {
|
||||
// Not inside this editor
|
||||
return;
|
||||
@ -330,20 +335,30 @@ class QueryField extends React.Component<any, any> {
|
||||
}
|
||||
|
||||
onKeyDown = (event, change) => {
|
||||
if (this.menuEl) {
|
||||
const { typeaheadIndex, suggestions } = this.state;
|
||||
const { typeaheadIndex, suggestions } = this.state;
|
||||
|
||||
switch (event.key) {
|
||||
case 'Escape': {
|
||||
if (this.menuEl) {
|
||||
event.preventDefault();
|
||||
this.resetTypeahead();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
switch (event.key) {
|
||||
case 'Escape': {
|
||||
if (this.menuEl) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.resetTypeahead();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'Tab': {
|
||||
case ' ': {
|
||||
if (event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
this.handleTypeahead();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'Tab': {
|
||||
if (this.menuEl) {
|
||||
// Dont blur input
|
||||
event.preventDefault();
|
||||
if (!suggestions || suggestions.length === 0) {
|
||||
@ -359,25 +374,30 @@ class QueryField extends React.Component<any, any> {
|
||||
this.applyTypeahead(change, suggestion);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ArrowDown': {
|
||||
case 'ArrowDown': {
|
||||
if (this.menuEl) {
|
||||
// Select next suggestion
|
||||
event.preventDefault();
|
||||
this.setState({ typeaheadIndex: typeaheadIndex + 1 });
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ArrowUp': {
|
||||
case 'ArrowUp': {
|
||||
if (this.menuEl) {
|
||||
// Select previous suggestion
|
||||
event.preventDefault();
|
||||
this.setState({ typeaheadIndex: Math.max(0, typeaheadIndex - 1) });
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
// console.log('default key', event.key, event.which, event.charCode, event.locale, data.key);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// console.log('default key', event.key, event.which, event.charCode, event.locale, data.key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
@ -502,10 +522,17 @@ class QueryField extends React.Component<any, any> {
|
||||
|
||||
// Align menu overlay to editor node
|
||||
if (node) {
|
||||
// Read from DOM
|
||||
const rect = node.parentElement.getBoundingClientRect();
|
||||
menu.style.opacity = 1;
|
||||
menu.style.top = `${rect.top + window.scrollY + rect.height + 4}px`;
|
||||
menu.style.left = `${rect.left + window.scrollX - 2}px`;
|
||||
const scrollX = window.scrollX;
|
||||
const scrollY = window.scrollY;
|
||||
|
||||
// Write DOM
|
||||
requestAnimationFrame(() => {
|
||||
menu.style.opacity = 1;
|
||||
menu.style.top = `${rect.top + scrollY + rect.height + 4}px`;
|
||||
menu.style.left = `${rect.left + scrollX - 2}px`;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -514,6 +541,7 @@ class QueryField extends React.Component<any, any> {
|
||||
};
|
||||
|
||||
renderMenu = () => {
|
||||
const { portalPrefix } = this.props;
|
||||
const { suggestions } = this.state;
|
||||
const hasSuggesstions = suggestions && suggestions.length > 0;
|
||||
if (!hasSuggesstions) {
|
||||
@ -524,11 +552,13 @@ class QueryField extends React.Component<any, any> {
|
||||
let selectedIndex = Math.max(this.state.typeaheadIndex, 0);
|
||||
const flattenedSuggestions = flattenSuggestions(suggestions);
|
||||
selectedIndex = selectedIndex % flattenedSuggestions.length || 0;
|
||||
const selectedKeys = flattenedSuggestions.length > 0 ? [flattenedSuggestions[selectedIndex]] : [];
|
||||
const selectedKeys = (flattenedSuggestions.length > 0 ? [flattenedSuggestions[selectedIndex]] : []).map(
|
||||
i => (typeof i === 'object' ? i.text : i)
|
||||
);
|
||||
|
||||
// Create typeahead in DOM root so we can later position it absolutely
|
||||
return (
|
||||
<Portal>
|
||||
<Portal prefix={portalPrefix}>
|
||||
<Typeahead
|
||||
menuRef={this.menuRef}
|
||||
selectedItems={selectedKeys}
|
||||
@ -541,7 +571,7 @@ class QueryField extends React.Component<any, any> {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="query-field">
|
||||
<div className="slate-query-field">
|
||||
{this.renderMenu()}
|
||||
<Editor
|
||||
autoCorrect={false}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import promql from './slate-plugins/prism/promql';
|
||||
import QueryField from './QueryField';
|
||||
|
||||
class QueryRow extends PureComponent<any, any> {
|
||||
@ -55,12 +56,15 @@ class QueryRow extends PureComponent<any, any> {
|
||||
<i className="fa fa-minus" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="query-field-wrapper">
|
||||
<div className="slate-query-field-wrapper">
|
||||
<QueryField
|
||||
initialQuery={edited ? null : query}
|
||||
portalPrefix="explore"
|
||||
onPressEnter={this.handlePressEnter}
|
||||
onQueryChange={this.handleChangeQuery}
|
||||
placeholder="Enter a PromQL query"
|
||||
prismLanguage="promql"
|
||||
prismDefinition={promql}
|
||||
request={request}
|
||||
/>
|
||||
</div>
|
||||
|
@ -23,12 +23,13 @@ class TypeaheadItem extends React.PureComponent<any, any> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isSelected, label, onClickItem } = this.props;
|
||||
const { hint, isSelected, label, onClickItem } = this.props;
|
||||
const className = isSelected ? 'typeahead-item typeahead-item__selected' : 'typeahead-item';
|
||||
const onClick = () => onClickItem(label);
|
||||
return (
|
||||
<li ref={this.getRef} className={className} onClick={onClick}>
|
||||
{label}
|
||||
{hint && isSelected ? <div className="typeahead-item-hint">{hint}</div> : null}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
@ -41,9 +42,19 @@ class TypeaheadGroup extends React.PureComponent<any, any> {
|
||||
<li className="typeahead-group">
|
||||
<div className="typeahead-group__title">{label}</div>
|
||||
<ul className="typeahead-group__list">
|
||||
{items.map(item => (
|
||||
<TypeaheadItem key={item} onClickItem={onClickItem} isSelected={selected.indexOf(item) > -1} label={item} />
|
||||
))}
|
||||
{items.map(item => {
|
||||
const text = typeof item === 'object' ? item.text : item;
|
||||
const label = typeof item === 'object' ? item.display || item.text : item;
|
||||
return (
|
||||
<TypeaheadItem
|
||||
key={text}
|
||||
onClickItem={onClickItem}
|
||||
isSelected={selected.indexOf(text) > -1}
|
||||
hint={item.hint}
|
||||
label={label}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</li>
|
||||
);
|
||||
|
@ -1,16 +1,12 @@
|
||||
import React from 'react';
|
||||
import Prism from 'prismjs';
|
||||
|
||||
import Promql from './promql';
|
||||
|
||||
Prism.languages.promql = Promql;
|
||||
|
||||
const TOKEN_MARK = 'prism-token';
|
||||
|
||||
export function configurePrismMetricsTokens(metrics) {
|
||||
Prism.languages.promql.metric = {
|
||||
alias: 'variable',
|
||||
pattern: new RegExp(`(?:^|\\s)(${metrics.join('|')})(?:$|\\s)`),
|
||||
export function setPrismTokens(language, field, values, alias = 'variable') {
|
||||
Prism.languages[language][field] = {
|
||||
alias,
|
||||
pattern: new RegExp(`(?:^|\\s)(${values.join('|')})(?:$|\\s)`),
|
||||
};
|
||||
}
|
||||
|
||||
@ -21,7 +17,12 @@ export function configurePrismMetricsTokens(metrics) {
|
||||
* (Adapted to handle nested grammar definitions.)
|
||||
*/
|
||||
|
||||
export default function PrismPlugin() {
|
||||
export default function PrismPlugin({ definition, language }) {
|
||||
if (definition) {
|
||||
// Don't override exising modified definitions
|
||||
Prism.languages[language] = Prism.languages[language] || definition;
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Render a Slate mark with appropiate CSS class names
|
||||
@ -54,7 +55,7 @@ export default function PrismPlugin() {
|
||||
|
||||
const texts = node.getTexts().toArray();
|
||||
const tstring = texts.map(t => t.text).join('\n');
|
||||
const grammar = Prism.languages.promql;
|
||||
const grammar = Prism.languages[language];
|
||||
const tokens = Prism.tokenize(tstring, grammar);
|
||||
const decorations = [];
|
||||
let startText = texts.shift();
|
||||
|
@ -404,6 +404,7 @@ export default class CloudWatchDatasource {
|
||||
}
|
||||
|
||||
expandTemplateVariable(targets, scopedVars, templateSrv) {
|
||||
// Datasource and template srv logic uber-complected. This should be cleaned up.
|
||||
return _.chain(targets)
|
||||
.map(target => {
|
||||
var dimensionKey = _.findKey(target.dimensions, v => {
|
||||
|
@ -1,32 +1,38 @@
|
||||
import '../datasource';
|
||||
import { describe, beforeEach, it, expect, angularMocks } from 'test/lib/common';
|
||||
import helpers from 'test/specs/helpers';
|
||||
import CloudWatchDatasource from '../datasource';
|
||||
import 'app/features/dashboard/time_srv';
|
||||
import * as dateMath from 'app/core/utils/datemath';
|
||||
import _ from 'lodash';
|
||||
|
||||
describe('CloudWatchDatasource', function() {
|
||||
var ctx = new helpers.ServiceTestContext();
|
||||
var instanceSettings = {
|
||||
let instanceSettings = {
|
||||
jsonData: { defaultRegion: 'us-east-1', access: 'proxy' },
|
||||
};
|
||||
|
||||
beforeEach(angularMocks.module('grafana.core'));
|
||||
beforeEach(angularMocks.module('grafana.services'));
|
||||
beforeEach(angularMocks.module('grafana.controllers'));
|
||||
beforeEach(ctx.providePhase(['templateSrv', 'backendSrv']));
|
||||
beforeEach(ctx.createService('timeSrv'));
|
||||
let templateSrv = {
|
||||
data: {},
|
||||
templateSettings: { interpolate: /\[\[([\s\S]+?)\]\]/g },
|
||||
replace: text => _.template(text, templateSrv.templateSettings)(templateSrv.data),
|
||||
variableExists: () => false,
|
||||
};
|
||||
|
||||
beforeEach(
|
||||
angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) {
|
||||
ctx.$q = $q;
|
||||
ctx.$httpBackend = $httpBackend;
|
||||
ctx.$rootScope = $rootScope;
|
||||
ctx.ds = $injector.instantiate(CloudWatchDatasource, {
|
||||
instanceSettings: instanceSettings,
|
||||
});
|
||||
$httpBackend.when('GET', /\.html$/).respond('');
|
||||
})
|
||||
);
|
||||
let timeSrv = {
|
||||
time: { from: 'now-1h', to: 'now' },
|
||||
timeRange: () => {
|
||||
return {
|
||||
from: dateMath.parse(timeSrv.time.from, false),
|
||||
to: dateMath.parse(timeSrv.time.to, true),
|
||||
};
|
||||
},
|
||||
};
|
||||
let backendSrv = {};
|
||||
let ctx = <any>{
|
||||
backendSrv,
|
||||
templateSrv,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.ds = new CloudWatchDatasource(instanceSettings, {}, backendSrv, templateSrv, timeSrv);
|
||||
});
|
||||
|
||||
describe('When performing CloudWatch query', function() {
|
||||
var requestParams;
|
||||
@ -67,24 +73,23 @@ describe('CloudWatchDatasource', function() {
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
ctx.backendSrv.datasourceRequest = function(params) {
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(params => {
|
||||
requestParams = params.data;
|
||||
return ctx.$q.when({ data: response });
|
||||
};
|
||||
return Promise.resolve({ data: response });
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate the correct query', function(done) {
|
||||
ctx.ds.query(query).then(function() {
|
||||
var params = requestParams.queries[0];
|
||||
expect(params.namespace).to.be(query.targets[0].namespace);
|
||||
expect(params.metricName).to.be(query.targets[0].metricName);
|
||||
expect(params.dimensions['InstanceId']).to.be('i-12345678');
|
||||
expect(params.statistics).to.eql(query.targets[0].statistics);
|
||||
expect(params.period).to.be(query.targets[0].period);
|
||||
expect(params.namespace).toBe(query.targets[0].namespace);
|
||||
expect(params.metricName).toBe(query.targets[0].metricName);
|
||||
expect(params.dimensions['InstanceId']).toBe('i-12345678');
|
||||
expect(params.statistics).toEqual(query.targets[0].statistics);
|
||||
expect(params.period).toBe(query.targets[0].period);
|
||||
done();
|
||||
});
|
||||
ctx.$rootScope.$apply();
|
||||
});
|
||||
|
||||
it('should generate the correct query with interval variable', function(done) {
|
||||
@ -111,116 +116,17 @@ describe('CloudWatchDatasource', function() {
|
||||
|
||||
ctx.ds.query(query).then(function() {
|
||||
var params = requestParams.queries[0];
|
||||
expect(params.period).to.be('600');
|
||||
expect(params.period).toBe('600');
|
||||
done();
|
||||
});
|
||||
ctx.$rootScope.$apply();
|
||||
});
|
||||
|
||||
it('should return series list', function(done) {
|
||||
ctx.ds.query(query).then(function(result) {
|
||||
expect(result.data[0].target).to.be(response.results.A.series[0].name);
|
||||
expect(result.data[0].datapoints[0][0]).to.be(response.results.A.series[0].points[0][0]);
|
||||
expect(result.data[0].target).toBe(response.results.A.series[0].name);
|
||||
expect(result.data[0].datapoints[0][0]).toBe(response.results.A.series[0].points[0][0]);
|
||||
done();
|
||||
});
|
||||
ctx.$rootScope.$apply();
|
||||
});
|
||||
|
||||
it('should generate the correct targets by expanding template variables', function() {
|
||||
var templateSrv = {
|
||||
variables: [
|
||||
{
|
||||
name: 'instance_id',
|
||||
options: [
|
||||
{ text: 'i-23456789', value: 'i-23456789', selected: false },
|
||||
{ text: 'i-34567890', value: 'i-34567890', selected: true },
|
||||
],
|
||||
current: {
|
||||
text: 'i-34567890',
|
||||
value: 'i-34567890',
|
||||
},
|
||||
},
|
||||
],
|
||||
replace: function(target, scopedVars) {
|
||||
if (target === '$instance_id' && scopedVars['instance_id']['text'] === 'i-34567890') {
|
||||
return 'i-34567890';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
getVariableName: function(e) {
|
||||
return 'instance_id';
|
||||
},
|
||||
variableExists: function(e) {
|
||||
return true;
|
||||
},
|
||||
containsVariable: function(str, variableName) {
|
||||
return str.indexOf('$' + variableName) !== -1;
|
||||
},
|
||||
};
|
||||
|
||||
var targets = [
|
||||
{
|
||||
region: 'us-east-1',
|
||||
namespace: 'AWS/EC2',
|
||||
metricName: 'CPUUtilization',
|
||||
dimensions: {
|
||||
InstanceId: '$instance_id',
|
||||
},
|
||||
statistics: ['Average'],
|
||||
period: 300,
|
||||
},
|
||||
];
|
||||
|
||||
var result = ctx.ds.expandTemplateVariable(targets, {}, templateSrv);
|
||||
expect(result[0].dimensions.InstanceId).to.be('i-34567890');
|
||||
});
|
||||
|
||||
it('should generate the correct targets by expanding template variables from url', function() {
|
||||
var templateSrv = {
|
||||
variables: [
|
||||
{
|
||||
name: 'instance_id',
|
||||
options: [
|
||||
{ text: 'i-23456789', value: 'i-23456789', selected: false },
|
||||
{ text: 'i-34567890', value: 'i-34567890', selected: false },
|
||||
],
|
||||
current: 'i-45678901',
|
||||
},
|
||||
],
|
||||
replace: function(target, scopedVars) {
|
||||
if (target === '$instance_id') {
|
||||
return 'i-45678901';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
getVariableName: function(e) {
|
||||
return 'instance_id';
|
||||
},
|
||||
variableExists: function(e) {
|
||||
return true;
|
||||
},
|
||||
containsVariable: function(str, variableName) {
|
||||
return str.indexOf('$' + variableName) !== -1;
|
||||
},
|
||||
};
|
||||
|
||||
var targets = [
|
||||
{
|
||||
region: 'us-east-1',
|
||||
namespace: 'AWS/EC2',
|
||||
metricName: 'CPUUtilization',
|
||||
dimensions: {
|
||||
InstanceId: '$instance_id',
|
||||
},
|
||||
statistics: ['Average'],
|
||||
period: 300,
|
||||
},
|
||||
];
|
||||
|
||||
var result = ctx.ds.expandTemplateVariable(targets, {}, templateSrv);
|
||||
expect(result[0].dimensions.InstanceId).to.be('i-45678901');
|
||||
});
|
||||
});
|
||||
|
||||
@ -228,21 +134,21 @@ describe('CloudWatchDatasource', function() {
|
||||
it('should return the datasource region if empty or "default"', function() {
|
||||
var defaultRegion = instanceSettings.jsonData.defaultRegion;
|
||||
|
||||
expect(ctx.ds.getActualRegion()).to.be(defaultRegion);
|
||||
expect(ctx.ds.getActualRegion('')).to.be(defaultRegion);
|
||||
expect(ctx.ds.getActualRegion('default')).to.be(defaultRegion);
|
||||
expect(ctx.ds.getActualRegion()).toBe(defaultRegion);
|
||||
expect(ctx.ds.getActualRegion('')).toBe(defaultRegion);
|
||||
expect(ctx.ds.getActualRegion('default')).toBe(defaultRegion);
|
||||
});
|
||||
|
||||
it('should return the specified region if specified', function() {
|
||||
expect(ctx.ds.getActualRegion('some-fake-region-1')).to.be('some-fake-region-1');
|
||||
expect(ctx.ds.getActualRegion('some-fake-region-1')).toBe('some-fake-region-1');
|
||||
});
|
||||
|
||||
var requestParams;
|
||||
beforeEach(function() {
|
||||
ctx.ds.performTimeSeriesQuery = function(request) {
|
||||
ctx.ds.performTimeSeriesQuery = jest.fn(request => {
|
||||
requestParams = request;
|
||||
return ctx.$q.when({ data: {} });
|
||||
};
|
||||
return Promise.resolve({ data: {} });
|
||||
});
|
||||
});
|
||||
|
||||
it('should query for the datasource region if empty or "default"', function(done) {
|
||||
@ -264,10 +170,9 @@ describe('CloudWatchDatasource', function() {
|
||||
};
|
||||
|
||||
ctx.ds.query(query).then(function(result) {
|
||||
expect(requestParams.queries[0].region).to.be(instanceSettings.jsonData.defaultRegion);
|
||||
expect(requestParams.queries[0].region).toBe(instanceSettings.jsonData.defaultRegion);
|
||||
done();
|
||||
});
|
||||
ctx.$rootScope.$apply();
|
||||
});
|
||||
});
|
||||
|
||||
@ -311,18 +216,17 @@ describe('CloudWatchDatasource', function() {
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
ctx.backendSrv.datasourceRequest = function(params) {
|
||||
return ctx.$q.when({ data: response });
|
||||
};
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(params => {
|
||||
return Promise.resolve({ data: response });
|
||||
});
|
||||
});
|
||||
|
||||
it('should return series list', function(done) {
|
||||
ctx.ds.query(query).then(function(result) {
|
||||
expect(result.data[0].target).to.be(response.results.A.series[0].name);
|
||||
expect(result.data[0].datapoints[0][0]).to.be(response.results.A.series[0].points[0][0]);
|
||||
expect(result.data[0].target).toBe(response.results.A.series[0].name);
|
||||
expect(result.data[0].datapoints[0][0]).toBe(response.results.A.series[0].points[0][0]);
|
||||
done();
|
||||
});
|
||||
ctx.$rootScope.$apply();
|
||||
});
|
||||
});
|
||||
|
||||
@ -332,14 +236,13 @@ describe('CloudWatchDatasource', function() {
|
||||
scenario.setup = setupCallback => {
|
||||
beforeEach(() => {
|
||||
setupCallback();
|
||||
ctx.backendSrv.datasourceRequest = args => {
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(args => {
|
||||
scenario.request = args.data;
|
||||
return ctx.$q.when({ data: scenario.requestResponse });
|
||||
};
|
||||
return Promise.resolve({ data: scenario.requestResponse });
|
||||
});
|
||||
ctx.ds.metricFindQuery(query).then(args => {
|
||||
scenario.result = args;
|
||||
});
|
||||
ctx.$rootScope.$apply();
|
||||
});
|
||||
};
|
||||
|
||||
@ -359,9 +262,9 @@ describe('CloudWatchDatasource', function() {
|
||||
});
|
||||
|
||||
it('should call __GetRegions and return result', () => {
|
||||
expect(scenario.result[0].text).to.contain('us-east-1');
|
||||
expect(scenario.request.queries[0].type).to.be('metricFindQuery');
|
||||
expect(scenario.request.queries[0].subtype).to.be('regions');
|
||||
expect(scenario.result[0].text).toContain('us-east-1');
|
||||
expect(scenario.request.queries[0].type).toBe('metricFindQuery');
|
||||
expect(scenario.request.queries[0].subtype).toBe('regions');
|
||||
});
|
||||
});
|
||||
|
||||
@ -377,9 +280,9 @@ describe('CloudWatchDatasource', function() {
|
||||
});
|
||||
|
||||
it('should call __GetNamespaces and return result', () => {
|
||||
expect(scenario.result[0].text).to.contain('AWS/EC2');
|
||||
expect(scenario.request.queries[0].type).to.be('metricFindQuery');
|
||||
expect(scenario.request.queries[0].subtype).to.be('namespaces');
|
||||
expect(scenario.result[0].text).toContain('AWS/EC2');
|
||||
expect(scenario.request.queries[0].type).toBe('metricFindQuery');
|
||||
expect(scenario.request.queries[0].subtype).toBe('namespaces');
|
||||
});
|
||||
});
|
||||
|
||||
@ -395,9 +298,9 @@ describe('CloudWatchDatasource', function() {
|
||||
});
|
||||
|
||||
it('should call __GetMetrics and return result', () => {
|
||||
expect(scenario.result[0].text).to.be('CPUUtilization');
|
||||
expect(scenario.request.queries[0].type).to.be('metricFindQuery');
|
||||
expect(scenario.request.queries[0].subtype).to.be('metrics');
|
||||
expect(scenario.result[0].text).toBe('CPUUtilization');
|
||||
expect(scenario.request.queries[0].type).toBe('metricFindQuery');
|
||||
expect(scenario.request.queries[0].subtype).toBe('metrics');
|
||||
});
|
||||
});
|
||||
|
||||
@ -413,9 +316,9 @@ describe('CloudWatchDatasource', function() {
|
||||
});
|
||||
|
||||
it('should call __GetDimensions and return result', () => {
|
||||
expect(scenario.result[0].text).to.be('InstanceId');
|
||||
expect(scenario.request.queries[0].type).to.be('metricFindQuery');
|
||||
expect(scenario.request.queries[0].subtype).to.be('dimension_keys');
|
||||
expect(scenario.result[0].text).toBe('InstanceId');
|
||||
expect(scenario.request.queries[0].type).toBe('metricFindQuery');
|
||||
expect(scenario.request.queries[0].subtype).toBe('dimension_keys');
|
||||
});
|
||||
});
|
||||
|
||||
@ -431,9 +334,9 @@ describe('CloudWatchDatasource', function() {
|
||||
});
|
||||
|
||||
it('should call __ListMetrics and return result', () => {
|
||||
expect(scenario.result[0].text).to.contain('i-12345678');
|
||||
expect(scenario.request.queries[0].type).to.be('metricFindQuery');
|
||||
expect(scenario.request.queries[0].subtype).to.be('dimension_values');
|
||||
expect(scenario.result[0].text).toContain('i-12345678');
|
||||
expect(scenario.request.queries[0].type).toBe('metricFindQuery');
|
||||
expect(scenario.request.queries[0].subtype).toBe('dimension_values');
|
||||
});
|
||||
});
|
||||
|
||||
@ -449,9 +352,9 @@ describe('CloudWatchDatasource', function() {
|
||||
});
|
||||
|
||||
it('should call __ListMetrics and return result', () => {
|
||||
expect(scenario.result[0].text).to.contain('i-12345678');
|
||||
expect(scenario.request.queries[0].type).to.be('metricFindQuery');
|
||||
expect(scenario.request.queries[0].subtype).to.be('dimension_values');
|
||||
expect(scenario.result[0].text).toContain('i-12345678');
|
||||
expect(scenario.request.queries[0].type).toBe('metricFindQuery');
|
||||
expect(scenario.request.queries[0].subtype).toBe('dimension_values');
|
||||
});
|
||||
});
|
||||
|
||||
@ -544,7 +447,7 @@ describe('CloudWatchDatasource', function() {
|
||||
let now = new Date(options.range.from.valueOf() + t[2] * 1000);
|
||||
let expected = t[3];
|
||||
let actual = ctx.ds.getPeriod(target, options, now);
|
||||
expect(actual).to.be(expected);
|
||||
expect(actual).toBe(expected);
|
||||
}
|
||||
});
|
||||
});
|
@ -1,28 +1,21 @@
|
||||
import { describe, beforeEach, it, expect, angularMocks } from 'test/lib/common';
|
||||
import moment from 'moment';
|
||||
import helpers from 'test/specs/helpers';
|
||||
import { MysqlDatasource } from '../datasource';
|
||||
import { CustomVariable } from 'app/features/templating/custom_variable';
|
||||
|
||||
describe('MySQLDatasource', function() {
|
||||
var ctx = new helpers.ServiceTestContext();
|
||||
var instanceSettings = { name: 'mysql' };
|
||||
let instanceSettings = { name: 'mysql' };
|
||||
let backendSrv = {};
|
||||
let templateSrv = {
|
||||
replace: jest.fn(text => text),
|
||||
};
|
||||
|
||||
beforeEach(angularMocks.module('grafana.core'));
|
||||
beforeEach(angularMocks.module('grafana.services'));
|
||||
beforeEach(ctx.providePhase(['backendSrv']));
|
||||
let ctx = <any>{
|
||||
backendSrv,
|
||||
};
|
||||
|
||||
beforeEach(
|
||||
angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) {
|
||||
ctx.$q = $q;
|
||||
ctx.$httpBackend = $httpBackend;
|
||||
ctx.$rootScope = $rootScope;
|
||||
ctx.ds = $injector.instantiate(MysqlDatasource, {
|
||||
instanceSettings: instanceSettings,
|
||||
});
|
||||
$httpBackend.when('GET', /\.html$/).respond('');
|
||||
})
|
||||
);
|
||||
beforeEach(() => {
|
||||
ctx.ds = new MysqlDatasource(instanceSettings, backendSrv, {}, templateSrv);
|
||||
});
|
||||
|
||||
describe('When performing annotationQuery', function() {
|
||||
let results;
|
||||
@ -59,26 +52,25 @@ describe('MySQLDatasource', function() {
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
ctx.backendSrv.datasourceRequest = function(options) {
|
||||
return ctx.$q.when({ data: response, status: 200 });
|
||||
};
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
||||
return Promise.resolve({ data: response, status: 200 });
|
||||
});
|
||||
ctx.ds.annotationQuery(options).then(function(data) {
|
||||
results = data;
|
||||
});
|
||||
ctx.$rootScope.$apply();
|
||||
});
|
||||
|
||||
it('should return annotation list', function() {
|
||||
expect(results.length).to.be(3);
|
||||
expect(results.length).toBe(3);
|
||||
|
||||
expect(results[0].text).to.be('some text');
|
||||
expect(results[0].tags[0]).to.be('TagA');
|
||||
expect(results[0].tags[1]).to.be('TagB');
|
||||
expect(results[0].text).toBe('some text');
|
||||
expect(results[0].tags[0]).toBe('TagA');
|
||||
expect(results[0].tags[1]).toBe('TagB');
|
||||
|
||||
expect(results[1].tags[0]).to.be('TagB');
|
||||
expect(results[1].tags[1]).to.be('TagC');
|
||||
expect(results[1].tags[0]).toBe('TagB');
|
||||
expect(results[1].tags[1]).toBe('TagC');
|
||||
|
||||
expect(results[2].tags.length).to.be(0);
|
||||
expect(results[2].tags.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@ -103,19 +95,18 @@ describe('MySQLDatasource', function() {
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
ctx.backendSrv.datasourceRequest = function(options) {
|
||||
return ctx.$q.when({ data: response, status: 200 });
|
||||
};
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
||||
return Promise.resolve({ 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');
|
||||
expect(results.length).toBe(6);
|
||||
expect(results[0].text).toBe('aTitle');
|
||||
expect(results[5].text).toBe('some text3');
|
||||
});
|
||||
});
|
||||
|
||||
@ -140,21 +131,20 @@ describe('MySQLDatasource', function() {
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
ctx.backendSrv.datasourceRequest = function(options) {
|
||||
return ctx.$q.when({ data: response, status: 200 });
|
||||
};
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
||||
return Promise.resolve({ 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');
|
||||
expect(results.length).toBe(3);
|
||||
expect(results[0].text).toBe('aTitle');
|
||||
expect(results[0].value).toBe('value1');
|
||||
expect(results[2].text).toBe('aTitle3');
|
||||
expect(results[2].value).toBe('value3');
|
||||
});
|
||||
});
|
||||
|
||||
@ -179,19 +169,18 @@ describe('MySQLDatasource', function() {
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
ctx.backendSrv.datasourceRequest = function(options) {
|
||||
return ctx.$q.when({ data: response, status: 200 });
|
||||
};
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
||||
return Promise.resolve({ 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');
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0].text).toBe('aTitle');
|
||||
expect(results[0].value).toBe('same');
|
||||
});
|
||||
});
|
||||
|
||||
@ -202,33 +191,33 @@ describe('MySQLDatasource', function() {
|
||||
|
||||
describe('and value is a string', () => {
|
||||
it('should return an unquoted value', () => {
|
||||
expect(ctx.ds.interpolateVariable('abc', ctx.variable)).to.eql('abc');
|
||||
expect(ctx.ds.interpolateVariable('abc', ctx.variable)).toEqual('abc');
|
||||
});
|
||||
});
|
||||
|
||||
describe('and value is a number', () => {
|
||||
it('should return an unquoted value', () => {
|
||||
expect(ctx.ds.interpolateVariable(1000, ctx.variable)).to.eql(1000);
|
||||
expect(ctx.ds.interpolateVariable(1000, ctx.variable)).toEqual(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('and value is an array of strings', () => {
|
||||
it('should return comma separated quoted values', () => {
|
||||
expect(ctx.ds.interpolateVariable(['a', 'b', 'c'], ctx.variable)).to.eql("'a','b','c'");
|
||||
expect(ctx.ds.interpolateVariable(['a', 'b', 'c'], ctx.variable)).toEqual("'a','b','c'");
|
||||
});
|
||||
});
|
||||
|
||||
describe('and variable allows multi-value and value is a string', () => {
|
||||
it('should return a quoted value', () => {
|
||||
ctx.variable.multi = true;
|
||||
expect(ctx.ds.interpolateVariable('abc', ctx.variable)).to.eql("'abc'");
|
||||
expect(ctx.ds.interpolateVariable('abc', ctx.variable)).toEqual("'abc'");
|
||||
});
|
||||
});
|
||||
|
||||
describe('and variable allows all and value is a string', () => {
|
||||
it('should return a quoted value', () => {
|
||||
ctx.variable.includeAll = true;
|
||||
expect(ctx.ds.interpolateVariable('abc', ctx.variable)).to.eql("'abc'");
|
||||
expect(ctx.ds.interpolateVariable('abc', ctx.variable)).toEqual("'abc'");
|
||||
});
|
||||
});
|
||||
});
|
@ -1,28 +1,21 @@
|
||||
import { describe, beforeEach, it, expect, angularMocks } from 'test/lib/common';
|
||||
import moment from 'moment';
|
||||
import helpers from 'test/specs/helpers';
|
||||
import { PostgresDatasource } from '../datasource';
|
||||
import { CustomVariable } from 'app/features/templating/custom_variable';
|
||||
|
||||
describe('PostgreSQLDatasource', function() {
|
||||
var ctx = new helpers.ServiceTestContext();
|
||||
var instanceSettings = { name: 'postgresql' };
|
||||
let instanceSettings = { name: 'postgresql' };
|
||||
|
||||
beforeEach(angularMocks.module('grafana.core'));
|
||||
beforeEach(angularMocks.module('grafana.services'));
|
||||
beforeEach(ctx.providePhase(['backendSrv']));
|
||||
let backendSrv = {};
|
||||
let templateSrv = {
|
||||
replace: jest.fn(text => text),
|
||||
};
|
||||
let ctx = <any>{
|
||||
backendSrv,
|
||||
};
|
||||
|
||||
beforeEach(
|
||||
angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) {
|
||||
ctx.$q = $q;
|
||||
ctx.$httpBackend = $httpBackend;
|
||||
ctx.$rootScope = $rootScope;
|
||||
ctx.ds = $injector.instantiate(PostgresDatasource, {
|
||||
instanceSettings: instanceSettings,
|
||||
});
|
||||
$httpBackend.when('GET', /\.html$/).respond('');
|
||||
})
|
||||
);
|
||||
beforeEach(() => {
|
||||
ctx.ds = new PostgresDatasource(instanceSettings, backendSrv, {}, templateSrv);
|
||||
});
|
||||
|
||||
describe('When performing annotationQuery', function() {
|
||||
let results;
|
||||
@ -59,26 +52,25 @@ describe('PostgreSQLDatasource', function() {
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
ctx.backendSrv.datasourceRequest = function(options) {
|
||||
return ctx.$q.when({ data: response, status: 200 });
|
||||
};
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
||||
return Promise.resolve({ data: response, status: 200 });
|
||||
});
|
||||
ctx.ds.annotationQuery(options).then(function(data) {
|
||||
results = data;
|
||||
});
|
||||
ctx.$rootScope.$apply();
|
||||
});
|
||||
|
||||
it('should return annotation list', function() {
|
||||
expect(results.length).to.be(3);
|
||||
expect(results.length).toBe(3);
|
||||
|
||||
expect(results[0].text).to.be('some text');
|
||||
expect(results[0].tags[0]).to.be('TagA');
|
||||
expect(results[0].tags[1]).to.be('TagB');
|
||||
expect(results[0].text).toBe('some text');
|
||||
expect(results[0].tags[0]).toBe('TagA');
|
||||
expect(results[0].tags[1]).toBe('TagB');
|
||||
|
||||
expect(results[1].tags[0]).to.be('TagB');
|
||||
expect(results[1].tags[1]).to.be('TagC');
|
||||
expect(results[1].tags[0]).toBe('TagB');
|
||||
expect(results[1].tags[1]).toBe('TagC');
|
||||
|
||||
expect(results[2].tags.length).to.be(0);
|
||||
expect(results[2].tags.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@ -103,19 +95,18 @@ describe('PostgreSQLDatasource', function() {
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
ctx.backendSrv.datasourceRequest = function(options) {
|
||||
return ctx.$q.when({ data: response, status: 200 });
|
||||
};
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
||||
return Promise.resolve({ 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');
|
||||
expect(results.length).toBe(6);
|
||||
expect(results[0].text).toBe('aTitle');
|
||||
expect(results[5].text).toBe('some text3');
|
||||
});
|
||||
});
|
||||
|
||||
@ -140,21 +131,20 @@ describe('PostgreSQLDatasource', function() {
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
ctx.backendSrv.datasourceRequest = function(options) {
|
||||
return ctx.$q.when({ data: response, status: 200 });
|
||||
};
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
||||
return Promise.resolve({ 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');
|
||||
expect(results.length).toBe(3);
|
||||
expect(results[0].text).toBe('aTitle');
|
||||
expect(results[0].value).toBe('value1');
|
||||
expect(results[2].text).toBe('aTitle3');
|
||||
expect(results[2].value).toBe('value3');
|
||||
});
|
||||
});
|
||||
|
||||
@ -178,20 +168,20 @@ describe('PostgreSQLDatasource', function() {
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
ctx.backendSrv.datasourceRequest = function(options) {
|
||||
return ctx.$q.when({ data: response, status: 200 });
|
||||
};
|
||||
beforeEach(() => {
|
||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
||||
return Promise.resolve({ data: response, status: 200 });
|
||||
});
|
||||
ctx.ds.metricFindQuery(query).then(function(data) {
|
||||
results = data;
|
||||
});
|
||||
ctx.$rootScope.$apply();
|
||||
//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');
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0].text).toBe('aTitle');
|
||||
expect(results[0].value).toBe('same');
|
||||
});
|
||||
});
|
||||
|
||||
@ -202,33 +192,33 @@ describe('PostgreSQLDatasource', function() {
|
||||
|
||||
describe('and value is a string', () => {
|
||||
it('should return an unquoted value', () => {
|
||||
expect(ctx.ds.interpolateVariable('abc', ctx.variable)).to.eql('abc');
|
||||
expect(ctx.ds.interpolateVariable('abc', ctx.variable)).toEqual('abc');
|
||||
});
|
||||
});
|
||||
|
||||
describe('and value is a number', () => {
|
||||
it('should return an unquoted value', () => {
|
||||
expect(ctx.ds.interpolateVariable(1000, ctx.variable)).to.eql(1000);
|
||||
expect(ctx.ds.interpolateVariable(1000, ctx.variable)).toEqual(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('and value is an array of strings', () => {
|
||||
it('should return comma separated quoted values', () => {
|
||||
expect(ctx.ds.interpolateVariable(['a', 'b', 'c'], ctx.variable)).to.eql("'a','b','c'");
|
||||
expect(ctx.ds.interpolateVariable(['a', 'b', 'c'], ctx.variable)).toEqual("'a','b','c'");
|
||||
});
|
||||
});
|
||||
|
||||
describe('and variable allows multi-value and is a string', () => {
|
||||
it('should return a quoted value', () => {
|
||||
ctx.variable.multi = true;
|
||||
expect(ctx.ds.interpolateVariable('abc', ctx.variable)).to.eql("'abc'");
|
||||
expect(ctx.ds.interpolateVariable('abc', ctx.variable)).toEqual("'abc'");
|
||||
});
|
||||
});
|
||||
|
||||
describe('and variable allows all and is a string', () => {
|
||||
it('should return a quoted value', () => {
|
||||
ctx.variable.includeAll = true;
|
||||
expect(ctx.ds.interpolateVariable('abc', ctx.variable)).to.eql("'abc'");
|
||||
expect(ctx.ds.interpolateVariable('abc', ctx.variable)).toEqual("'abc'");
|
||||
});
|
||||
});
|
||||
});
|
@ -3,7 +3,7 @@ import * as d3ScaleChromatic from 'd3-scale-chromatic';
|
||||
|
||||
export function getColorScale(colorScheme: any, lightTheme: boolean, maxValue: number, minValue = 0): (d: any) => any {
|
||||
let colorInterpolator = d3ScaleChromatic[colorScheme.value];
|
||||
let colorScaleInverted = colorScheme.invert === 'always' || (colorScheme.invert === 'dark' && !lightTheme);
|
||||
let colorScaleInverted = colorScheme.invert === 'always' || colorScheme.invert === (lightTheme ? 'light' : 'dark');
|
||||
|
||||
let start = colorScaleInverted ? maxValue : minValue;
|
||||
let end = colorScaleInverted ? minValue : maxValue;
|
||||
|
@ -76,6 +76,13 @@ let colorSchemes = [
|
||||
{ name: 'Reds', value: 'interpolateReds', invert: 'dark' },
|
||||
|
||||
// Sequential (Multi-Hue)
|
||||
{ name: 'Viridis', value: 'interpolateViridis', invert: 'light' },
|
||||
{ name: 'Magma', value: 'interpolateMagma', invert: 'light' },
|
||||
{ name: 'Inferno', value: 'interpolateInferno', invert: 'light' },
|
||||
{ name: 'Plasma', value: 'interpolatePlasma', invert: 'light' },
|
||||
{ name: 'Warm', value: 'interpolateWarm', invert: 'light' },
|
||||
{ name: 'Cool', value: 'interpolateCool', invert: 'light' },
|
||||
{ name: 'Cubehelix', value: 'interpolateCubehelixDefault', invert: 'light' },
|
||||
{ name: 'BuGn', value: 'interpolateBuGn', invert: 'dark' },
|
||||
{ name: 'BuPu', value: 'interpolateBuPu', invert: 'dark' },
|
||||
{ name: 'GnBu', value: 'interpolateGnBu', invert: 'dark' },
|
||||
@ -87,7 +94,7 @@ let colorSchemes = [
|
||||
{ name: 'YlGnBu', value: 'interpolateYlGnBu', invert: 'dark' },
|
||||
{ name: 'YlGn', value: 'interpolateYlGn', invert: 'dark' },
|
||||
{ name: 'YlOrBr', value: 'interpolateYlOrBr', invert: 'dark' },
|
||||
{ name: 'YlOrRd', value: 'interpolateYlOrRd', invert: 'darm' },
|
||||
{ name: 'YlOrRd', value: 'interpolateYlOrRd', invert: 'dark' },
|
||||
];
|
||||
|
||||
const ds_support_histogram_sort = ['prometheus', 'elasticsearch'];
|
||||
|
@ -67,6 +67,7 @@
|
||||
@import 'components/filter-list';
|
||||
@import 'components/filter-table';
|
||||
@import 'components/old_stuff';
|
||||
@import 'components/slate_editor';
|
||||
@import 'components/typeahead';
|
||||
@import 'components/modals';
|
||||
@import 'components/dropdown';
|
||||
|
151
public/sass/components/_slate_editor.scss
Normal file
151
public/sass/components/_slate_editor.scss
Normal file
@ -0,0 +1,151 @@
|
||||
.slate-query-field {
|
||||
font-size: $font-size-root;
|
||||
font-family: $font-family-monospace;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.slate-query-field-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
padding: 6px 7px 4px;
|
||||
width: 100%;
|
||||
cursor: text;
|
||||
line-height: $line-height-base;
|
||||
color: $text-color-weak;
|
||||
background-color: $panel-bg;
|
||||
background-image: none;
|
||||
border: $panel-border;
|
||||
border-radius: $border-radius;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.slate-typeahead {
|
||||
.typeahead {
|
||||
position: absolute;
|
||||
z-index: auto;
|
||||
top: -10000px;
|
||||
left: -10000px;
|
||||
opacity: 0;
|
||||
border-radius: $border-radius;
|
||||
transition: opacity 0.75s;
|
||||
border: $panel-border;
|
||||
max-height: calc(66vh);
|
||||
overflow-y: scroll;
|
||||
max-width: calc(66%);
|
||||
overflow-x: hidden;
|
||||
outline: none;
|
||||
list-style: none;
|
||||
background: $panel-bg;
|
||||
color: $text-color;
|
||||
transition: opacity 0.4s ease-out;
|
||||
box-shadow: $typeahead-shadow;
|
||||
}
|
||||
|
||||
.typeahead-group__title {
|
||||
color: $text-color-weak;
|
||||
font-size: $font-size-sm;
|
||||
line-height: $line-height-base;
|
||||
padding: $input-padding-y $input-padding-x;
|
||||
}
|
||||
|
||||
.typeahead-item {
|
||||
height: auto;
|
||||
font-family: $font-family-monospace;
|
||||
padding: $input-padding-y $input-padding-x;
|
||||
padding-left: $input-padding-x-lg;
|
||||
font-size: $font-size-sm;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), border-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
|
||||
background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), padding 0.15s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
}
|
||||
|
||||
.typeahead-item__selected {
|
||||
background-color: $typeahead-selected-bg;
|
||||
color: $typeahead-selected-color;
|
||||
|
||||
.typeahead-item-hint {
|
||||
font-size: $font-size-xs;
|
||||
color: $text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* SYNTAX */
|
||||
|
||||
.slate-query-field {
|
||||
.token.comment,
|
||||
.token.block-comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: $text-color-weak;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: $text-color-weak;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.function-name,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: $query-red;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.function,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: $query-green;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.token.variable {
|
||||
color: $query-purple;
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword,
|
||||
.token.class-name {
|
||||
color: $query-blue;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important {
|
||||
color: $query-orange;
|
||||
}
|
||||
|
||||
.token.important {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
@ -93,150 +93,3 @@
|
||||
.query-row-tools {
|
||||
width: 4rem;
|
||||
}
|
||||
|
||||
.query-field {
|
||||
font-size: $font-size-root;
|
||||
font-family: $font-family-monospace;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.query-field-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
padding: 6px 7px 4px;
|
||||
width: 100%;
|
||||
cursor: text;
|
||||
line-height: $line-height-base;
|
||||
color: $text-color-weak;
|
||||
background-color: $panel-bg;
|
||||
background-image: none;
|
||||
border: $panel-border;
|
||||
border-radius: $border-radius;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.explore-typeahead {
|
||||
.typeahead {
|
||||
position: absolute;
|
||||
z-index: auto;
|
||||
top: -10000px;
|
||||
left: -10000px;
|
||||
opacity: 0;
|
||||
border-radius: $border-radius;
|
||||
transition: opacity 0.75s;
|
||||
border: $panel-border;
|
||||
max-height: calc(66vh);
|
||||
overflow-y: scroll;
|
||||
max-width: calc(66%);
|
||||
overflow-x: hidden;
|
||||
outline: none;
|
||||
list-style: none;
|
||||
background: $panel-bg;
|
||||
color: $text-color;
|
||||
transition: opacity 0.4s ease-out;
|
||||
box-shadow: $typeahead-shadow;
|
||||
}
|
||||
|
||||
.typeahead-group__title {
|
||||
color: $text-color-weak;
|
||||
font-size: $font-size-sm;
|
||||
line-height: $line-height-base;
|
||||
padding: $input-padding-y $input-padding-x;
|
||||
}
|
||||
|
||||
.typeahead-item {
|
||||
height: auto;
|
||||
font-family: $font-family-monospace;
|
||||
padding: $input-padding-y $input-padding-x;
|
||||
padding-left: $input-padding-x-lg;
|
||||
font-size: $font-size-sm;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), border-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
|
||||
background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), padding 0.15s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
}
|
||||
|
||||
.typeahead-item__selected {
|
||||
background-color: $typeahead-selected-bg;
|
||||
color: $typeahead-selected-color;
|
||||
}
|
||||
}
|
||||
|
||||
/* SYNTAX */
|
||||
|
||||
.explore {
|
||||
.token.comment,
|
||||
.token.block-comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: $text-color-weak;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: $text-color-weak;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.function-name,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: $query-red;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.function,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: $query-green;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.token.variable {
|
||||
color: $query-purple;
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword,
|
||||
.token.class-name {
|
||||
color: $query-blue;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important {
|
||||
color: $query-orange;
|
||||
}
|
||||
|
||||
.token.important {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
@ -13,9 +13,6 @@ function exit_if_fail {
|
||||
echo "running go fmt"
|
||||
exit_if_fail test -z "$(gofmt -s -l ./pkg | tee /dev/stderr)"
|
||||
|
||||
echo "running go vet"
|
||||
exit_if_fail test -z "$(go vet ./pkg/... | tee /dev/stderr)"
|
||||
|
||||
echo "building backend with install to cache pkgs"
|
||||
exit_if_fail time go install ./pkg/cmd/grafana-server
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
"rules": {
|
||||
"no-string-throw": true,
|
||||
"no-unused-expression": true,
|
||||
"no-unused-variable": false,
|
||||
"no-unused-variable": false,
|
||||
"no-use-before-declare": false,
|
||||
"no-duplicate-variable": true,
|
||||
"curly": true,
|
||||
|
Loading…
Reference in New Issue
Block a user