Merge remote-tracking branch 'upstream/master' into postgres-query-builder

This commit is contained in:
Sven Klemm 2018-07-11 10:21:13 +02:00
commit 1d711924bc
24 changed files with 462 additions and 501 deletions

View File

@ -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:

View File

@ -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)

View File

@ -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",

View File

@ -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

View File

@ -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))
})
})
}

View File

@ -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 {

View File

@ -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

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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}

View File

@ -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>

View File

@ -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>
);

View File

@ -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();

View File

@ -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 => {

View File

@ -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);
}
});
});

View File

@ -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'");
});
});
});

View File

@ -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'");
});
});
});

View File

@ -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;

View File

@ -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'];

View File

@ -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';

View 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;
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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,