Merge branch 'master' into develop

This commit is contained in:
Torkel Ödegaard 2017-10-24 09:06:09 +02:00
commit b44daaabf2
69 changed files with 1284 additions and 654 deletions

View File

@ -1,10 +0,0 @@
#!/usr/bin/env bash
test -z "$(gofmt -s -l . | grep -v vendor/src/ | tee /dev/stderr)"
if [ $? -gt 0 ]; then
echo "Some files aren't formatted, please run 'go fmt ./pkg/...' to format your source code before committing"
exit 1
fi
grunt test

View File

@ -12,15 +12,16 @@
## New Features ## New Features
* **Data Source Proxy**: Add support for whitelisting specified cookies that will be passed through to the data source when proxying data source requests [#5457](https://github.com/grafana/grafana/issues/5457), thanks [@robingustafsson](https://github.com/robingustafsson) * **Data Source Proxy**: Add support for whitelisting specified cookies that will be passed through to the data source when proxying data source requests [#5457](https://github.com/grafana/grafana/issues/5457), thanks [@robingustafsson](https://github.com/robingustafsson)
* **Postgres**: modify group by time macro so it can be used in select clause [#9527](https://github.com/grafana/grafana/pull/9527), thanks [@svenklemm](https://github.com/svenklemm)
## Fixes ## Fixes
* **Sensu**: Send alert message to sensu output [#9551](https://github.com/grafana/grafana/issues/9551), thx [@cjchand](https://github.com/cjchand) * **Sensu**: Send alert message to sensu output [#9551](https://github.com/grafana/grafana/issues/9551), thx [@cjchand](https://github.com/cjchand)
# 4.6.0-beta3 (unreleased) # 4.6.0-beta3 (2017-10-23)
## Fixes
* **Prometheus**: Fix for browser crash for short time ranges. [#9575](https://github.com/grafana/grafana/issues/9575) * **Prometheus**: Fix for browser crash for short time ranges. [#9575](https://github.com/grafana/grafana/issues/9575)
* **Heatmap**: Fix for y-axis not showing. [#9576](https://github.com/grafana/grafana/issues/9576) * **Heatmap**: Fix for y-axis not showing. [#9576](https://github.com/grafana/grafana/issues/9576)
* **Save to file**: Fix for save to file in export modal. [#9586](https://github.com/grafana/grafana/issues/9586) * **Save to file**: Fix for save to file in export modal. [#9586](https://github.com/grafana/grafana/issues/9586)
* **Postgres**: modify group by time macro so it can be used in select clause [#9527](https://github.com/grafana/grafana/pull/9527), thanks [@svenklemm](https://github.com/svenklemm)
# 4.6.0-beta2 (2017-10-17) # 4.6.0-beta2 (2017-10-17)

View File

@ -1,6 +1,6 @@
machine: machine:
node: node:
version: 6.9.2 version: 6.11.4
python: python:
version: 2.7.3 version: 2.7.3
services: services:
@ -30,10 +30,10 @@ dependencies:
- sudo apt-get update; sudo apt-get install rpm; sudo apt-get install expect - sudo apt-get update; sudo apt-get install rpm; sudo apt-get install expect
- ./scripts/build/build_container.sh - ./scripts/build/build_container.sh
test: test:
override: override:
- bash scripts/circle-test.sh - bash scripts/circle-test-frontend.sh
- bash scripts/circle-test-backend.sh
deployment: deployment:
gh_branch: gh_branch:

View File

@ -1,6 +1,7 @@
+++ +++
title = "Data Sources" title = "Data Sources"
type = "docs" type = "docs"
aliases = ["/datasources/overview/"]
[menu.docs] [menu.docs]
name = "Data Sources" name = "Data Sources"
identifier = "datasources" identifier = "datasources"

View File

@ -68,6 +68,7 @@ This makes exploring and filtering Prometheus data much easier.
* **Opsgenie**: Use their latest API instead of old version [#9399](https://github.com/grafana/grafana/pull/9399), thx [@cglrkn](https://github.com/cglrkn) * **Opsgenie**: Use their latest API instead of old version [#9399](https://github.com/grafana/grafana/pull/9399), thx [@cglrkn](https://github.com/cglrkn)
* **Table**: Add support for displaying the timestamp with milliseconds [#9429](https://github.com/grafana/grafana/pull/9429), thx [@s1061123](https://github.com/s1061123) * **Table**: Add support for displaying the timestamp with milliseconds [#9429](https://github.com/grafana/grafana/pull/9429), thx [@s1061123](https://github.com/s1061123)
* **Hipchat**: Add metrics, message and image to hipchat notifications [#9110](https://github.com/grafana/grafana/issues/9110), thx [@eloo](https://github.com/eloo) * **Hipchat**: Add metrics, message and image to hipchat notifications [#9110](https://github.com/grafana/grafana/issues/9110), thx [@eloo](https://github.com/eloo)
* **Postgres**: modify group by time macro so it can be used in select clause [#9527](https://github.com/grafana/grafana/pull/9527), thanks [@svenklemm](https://github.com/svenklemm)
### Tech ### Tech
* **Go**: Grafana is now built using golang 1.9 * **Go**: Grafana is now built using golang 1.9

4
docs/yarn.lock Normal file
View File

@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1

View File

@ -4,7 +4,7 @@ module.exports = {
"transform": { "transform": {
"^.+\\.tsx?$": "<rootDir>/node_modules/ts-jest/preprocessor.js" "^.+\\.tsx?$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
}, },
"moduleDirectories": ["<rootDir>node_modules", "<rootDir>/public"], "moduleDirectories": ["<rootDir>/node_modules", "<rootDir>/public"],
"roots": [ "roots": [
"<rootDir>/public" "<rootDir>/public"
], ],

View File

@ -95,14 +95,15 @@
"zone.js": "^0.7.2" "zone.js": "^0.7.2"
}, },
"scripts": { "scripts": {
"dev": "./node_modules/.bin/webpack --progress --colors --config scripts/webpack/webpack.dev.js", "dev": "node ./node_modules/.bin/webpack --progress --colors --config scripts/webpack/webpack.dev.js",
"watch": "./node_modules/.bin/webpack --progress --colors --watch --config scripts/webpack/webpack.dev.js", "watch": "node ./node_modules/.bin/webpack --progress --colors --watch --config scripts/webpack/webpack.dev.js",
"build": "./node_modules/.bin/grunt build", "build": "node ./node_modules/.bin/grunt build",
"test": "./node_modules/.bin/grunt test", "test": "node ./node_modules/.bin/grunt test",
"test-ci": "./node_modules/.bin/grunt test --coverage=true", "test:coverage": "node ./node_modules/.bin/grunt test --coverage=true",
"lint": "./node_modules/.bin/tslint -c tslint.json --project tsconfig.json --type-check", "lint": "node ./node_modules/.bin/tslint -c tslint.json --project tsconfig.json --type-check",
"karma": "./node_modules/grunt-cli/bin/grunt karma:dev", "karma": "node ./node_modules/grunt-cli/bin/grunt karma:dev",
"jest": "./node_modules/jest-cli/bin/jest --notify --watch" "jest": "node ./node_modules/jest-cli/bin/jest.js --notify --watch",
"precommit": "node ./node_modules/grunt-cli/bin/grunt precommit"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@ -133,6 +134,8 @@
"rxjs": "^5.4.3", "rxjs": "^5.4.3",
"tether": "^1.4.0", "tether": "^1.4.0",
"tether-drop": "https://github.com/torkelo/drop", "tether-drop": "https://github.com/torkelo/drop",
"tinycolor2": "^1.4.1" "tinycolor2": "^1.4.1",
"d3": "^4.11.0",
"d3-scale-chromatic": "^1.1.1"
} }
} }

View File

@ -188,9 +188,8 @@ func (hs *HttpServer) metricsEndpoint(ctx *macaron.Context) {
return return
} }
promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{ promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{}).
DisableCompression: true, ServeHTTP(ctx.Resp, ctx.Req.Request)
}).ServeHTTP(ctx.Resp, ctx.Req.Request)
} }
func (hs *HttpServer) healthHandler(ctx *macaron.Context) { func (hs *HttpServer) healthHandler(ctx *macaron.Context) {

View File

@ -14,7 +14,7 @@ import (
"github.com/go-stack/stack" "github.com/go-stack/stack"
"github.com/inconshreveable/log15" "github.com/inconshreveable/log15"
"github.com/inconshreveable/log15/term" isatty "github.com/mattn/go-isatty"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
) )
@ -157,7 +157,7 @@ func getFilters(filterStrArray []string) map[string]log15.Lvl {
func getLogFormat(format string) log15.Format { func getLogFormat(format string) log15.Format {
switch format { switch format {
case "console": case "console":
if term.IsTty(os.Stdout.Fd()) { if isatty.IsTerminal(os.Stdout.Fd()) {
return log15.TerminalFormat() return log15.TerminalFormat()
} }
return log15.LogfmtFormat() return log15.LogfmtFormat()

View File

@ -21,6 +21,10 @@ func Gziper() macaron.Handler {
return return
} }
if strings.HasPrefix(requestPath, "/metrics") {
return
}
ctx.Invoke(macaronGziper) ctx.Invoke(macaronGziper)
} }
} }

View File

@ -40,7 +40,7 @@ func getPluginLogoUrl(pluginType, path, baseUrl string) string {
} }
func (fp *FrontendPluginBase) setPathsBasedOnApp(app *AppPlugin) { func (fp *FrontendPluginBase) setPathsBasedOnApp(app *AppPlugin) {
appSubPath := strings.Replace(fp.PluginDir, app.PluginDir, "", 1) appSubPath := strings.Replace(strings.Replace(fp.PluginDir, app.PluginDir, "", 1), "\\", "/", 1)
fp.IncludedInAppId = app.Id fp.IncludedInAppId = app.Id
fp.BaseUrl = app.BaseUrl fp.BaseUrl = app.BaseUrl

View File

@ -0,0 +1,34 @@
package plugins
import (
"testing"
"github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
)
func TestFrontendPlugin(t *testing.T) {
Convey("When setting paths based on App on Windows", t, func() {
setting.StaticRootPath = "c:\\grafana\\public"
fp := &FrontendPluginBase{
PluginBase: PluginBase{
PluginDir: "c:\\grafana\\public\\app\\plugins\\app\\testdata\\datasource",
BaseUrl: "fpbase",
},
}
app := &AppPlugin{
FrontendPluginBase: FrontendPluginBase{
PluginBase: PluginBase{
PluginDir: "c:\\grafana\\public\\app\\plugins\\app\\testdata",
Id: "testdata",
BaseUrl: "public/app/plugins/app/testdata",
},
},
}
fp.setPathsBasedOnApp(app)
So(fp.Module, ShouldEqual, "app/plugins/app/testdata/datasource/module")
})
}

View File

@ -27,7 +27,7 @@ _.move = function (array, fromIndex, toIndex) {
return array; return array;
}; };
import {coreModule} from './core/core'; import {coreModule, registerAngularDirectives} from './core/core';
export class GrafanaApp { export class GrafanaApp {
registerFunctions: any; registerFunctions: any;
@ -109,6 +109,9 @@ export class GrafanaApp {
// makes it possible to add dynamic stuff // makes it possible to add dynamic stuff
this.useModule(coreModule); this.useModule(coreModule);
// register react angular wrappers
registerAngularDirectives();
var preBootRequires = [System.import('app/features/all')]; var preBootRequires = [System.import('app/features/all')];
Promise.all(preBootRequires).then(() => { Promise.all(preBootRequires).then(() => {

View File

@ -1,5 +1,5 @@
import { react2AngularDirective } from 'app/core/utils/react2angular'; import { react2AngularDirective } from 'app/core/utils/react2angular';
import { PasswordStrength } from './ui/PasswordStrength'; import { PasswordStrength } from './components/PasswordStrength';
export function registerAngularDirectives() { export function registerAngularDirectives() {

View File

@ -6,7 +6,7 @@ export interface IProps {
onColorSelect: (c: string) => void; onColorSelect: (c: string) => void;
} }
export class GfColorPalette extends React.Component<IProps, any> { export class ColorPalette extends React.Component<IProps, any> {
paletteColors: string[]; paletteColors: string[];
constructor(props) { constructor(props) {

View File

@ -1,8 +1,8 @@
import React from 'react'; import React from 'react';
import $ from 'jquery'; import $ from 'jquery';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import { GfColorPalette } from './ColorPalette'; import { ColorPalette } from './ColorPalette';
import { GfSpectrumPicker } from './SpectrumPicker'; import { SpectrumPicker } from './SpectrumPicker';
const DEFAULT_COLOR = '#000000'; const DEFAULT_COLOR = '#000000';
@ -82,12 +82,12 @@ export class ColorPickerPopover extends React.Component<IProps, any> {
render() { render() {
const paletteTab = ( const paletteTab = (
<div id="palette"> <div id="palette">
<GfColorPalette color={this.state.color} onColorSelect={this.sampleColorSelected.bind(this)} /> <ColorPalette color={this.state.color} onColorSelect={this.sampleColorSelected.bind(this)} />
</div> </div>
); );
const spectrumTab = ( const spectrumTab = (
<div id="spectrum"> <div id="spectrum">
<GfSpectrumPicker color={this.state.color} onColorSelect={this.spectrumColorSelected.bind(this)} options={{}} /> <SpectrumPicker color={this.state.color} onColorSelect={this.spectrumColorSelected.bind(this)} options={{}} />
</div> </div>
); );
const currentTab = this.state.tab === 'palette' ? paletteTab : spectrumTab; const currentTab = this.state.tab === 'palette' ? paletteTab : spectrumTab;

View File

@ -9,7 +9,7 @@ export interface IProps {
onColorSelect: (c: string) => void; onColorSelect: (c: string) => void;
} }
export class GfSpectrumPicker extends React.Component<IProps, any> { export class SpectrumPicker extends React.Component<IProps, any> {
elem: any; elem: any;
isMoving: boolean; isMoving: boolean;

View File

@ -53,10 +53,9 @@ import {orgSwitcher} from './components/org_switcher';
import {profiler} from './profiler'; import {profiler} from './profiler';
import {registerAngularDirectives} from './angular_wrappers'; import {registerAngularDirectives} from './angular_wrappers';
registerAngularDirectives();
export { export {
profiler, profiler,
registerAngularDirectives,
arrayJoin, arrayJoin,
coreModule, coreModule,
grafanaAppDirective, grafanaAppDirective,

View File

@ -0,0 +1,10 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { ColorPalette } from '../components/colorpicker/ColorPalette';
describe('CollorPalette', () => {
it('renders correctly', () => {
const tree = renderer.create(<ColorPalette color="#EAB839" onColorSelect={jest.fn()} />).toJSON();
expect(tree).toMatchSnapshot();
});
});

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import {shallow} from 'enzyme'; import {shallow} from 'enzyme';
import {PasswordStrength} from '../ui/PasswordStrength'; import {PasswordStrength} from '../components/PasswordStrength';
describe('PasswordStrength', () => { describe('PasswordStrength', () => {

View File

@ -0,0 +1,628 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CollorPalette renders correctly 1`] = `
<div
className="graph-legend-popover"
>
<p
className="m-b-0"
>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#890f02",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#58140c",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#99440a",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#c15c17",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#967302",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#cca300",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#3f6833",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#2f575e",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#64b0c8",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#052b51",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#0a50a1",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#584477",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#3f2b5b",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#511749",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#e24d42",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#bf1b00",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#ef843c",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#f4d598",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#e5ac0e",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#9ac48a",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#508642",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#6ed0e0",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#65c5db",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#0a437c",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#447ebc",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#614d93",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#d683ce",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#6d1f62",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#ea6460",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#e0752d",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#f9934e",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#fceaca",
}
}
>
 
</i>
<i
className="pointer fa fa-circle-o"
onClick={[Function]}
style={
Object {
"color": "#eab839",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#b7dbab",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#629e51",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#70dbed",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#82b5d8",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#1f78c1",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#aea2e0",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#705da0",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#e5a8e2",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#962d82",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#f29191",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#fce2de",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#f9ba8f",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#f9e2d2",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#f2c96d",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#e0f9d7",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#7eb26d",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#cffaff",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#badff4",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#5195ce",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#dedaf7",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#806eb7",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#f9d9f9",
}
}
>
 
</i>
<i
className="pointer fa fa-circle"
onClick={[Function]}
style={
Object {
"color": "#ba43a9",
}
}
>
 
</i>
</p>
</div>
`;

View File

@ -1,5 +1,3 @@
///<reference path="../headers/common.d.ts" />
import kbn from 'app/core/utils/kbn'; import kbn from 'app/core/utils/kbn';
import _ from 'lodash'; import _ from 'lodash';
@ -23,6 +21,7 @@ export default class TimeSeries {
id: string; id: string;
label: string; label: string;
alias: string; alias: string;
aliasEscaped: string;
color: string; color: string;
valueFormater: any; valueFormater: any;
stats: any; stats: any;
@ -52,6 +51,7 @@ export default class TimeSeries {
this.label = opts.alias; this.label = opts.alias;
this.id = opts.alias; this.id = opts.alias;
this.alias = opts.alias; this.alias = opts.alias;
this.aliasEscaped = _.escape(opts.alias);
this.color = opts.color; this.color = opts.color;
this.valueFormater = kbn.valueFormats.none; this.valueFormater = kbn.valueFormats.none;
this.stats = {}; this.stats = {};

View File

@ -3,6 +3,7 @@ import './editor_ctrl';
import angular from 'angular'; import angular from 'angular';
import _ from 'lodash'; import _ from 'lodash';
import coreModule from 'app/core/core_module'; import coreModule from 'app/core/core_module';
import {makeRegions, dedupAnnotations} from './events_processing';
export class AnnotationsSrv { export class AnnotationsSrv {
globalAnnotationsPromise: any; globalAnnotationsPromise: any;
@ -161,83 +162,4 @@ export class AnnotationsSrv {
} }
} }
/**
* This function converts annotation events into set
* of single events and regions (event consist of two)
* @param annotations
* @param options
*/
function makeRegions(annotations, options) {
let [regionEvents, singleEvents] = _.partition(annotations, 'regionId');
let regions = getRegions(regionEvents, options.range);
annotations = _.concat(regions, singleEvents);
return annotations;
}
function getRegions(events, range) {
let region_events = _.filter(events, event => {
return event.regionId;
});
let regions = _.groupBy(region_events, 'regionId');
regions = _.compact(
_.map(regions, region_events => {
let region_obj = _.head(region_events);
if (region_events && region_events.length > 1) {
region_obj.timeEnd = region_events[1].time;
region_obj.isRegion = true;
return region_obj;
} else {
if (region_events && region_events.length) {
// Don't change proper region object
if (!region_obj.time || !region_obj.timeEnd) {
// This is cut region
if (isStartOfRegion(region_obj)) {
region_obj.timeEnd = range.to.valueOf() - 1;
} else {
// Start time = null
region_obj.timeEnd = region_obj.time;
region_obj.time = range.from.valueOf() + 1;
}
region_obj.isRegion = true;
}
return region_obj;
}
}
}),
);
return regions;
}
function isStartOfRegion(event): boolean {
return event.id && event.id === event.regionId;
}
function dedupAnnotations(annotations) {
let dedup = [];
// Split events by annotationId property existance
let events = _.partition(annotations, 'id');
let eventsById = _.groupBy(events[0], 'id');
dedup = _.map(eventsById, eventGroup => {
if (eventGroup.length > 1 && !_.every(eventGroup, isPanelAlert)) {
// Get first non-panel alert
return _.find(eventGroup, event => {
return event.eventType !== 'panel-alert';
});
} else {
return _.head(eventGroup);
}
});
dedup = _.concat(dedup, events[1]);
return dedup;
}
function isPanelAlert(event) {
return event.eventType === 'panel-alert';
}
coreModule.service('annotationsSrv', AnnotationsSrv); coreModule.service('annotationsSrv', AnnotationsSrv);

View File

@ -0,0 +1,80 @@
import _ from 'lodash';
/**
* This function converts annotation events into set
* of single events and regions (event consist of two)
* @param annotations
* @param options
*/
export function makeRegions(annotations, options) {
let [regionEvents, singleEvents] = _.partition(annotations, 'regionId');
let regions = getRegions(regionEvents, options.range);
annotations = _.concat(regions, singleEvents);
return annotations;
}
function getRegions(events, range) {
let region_events = _.filter(events, event => {
return event.regionId;
});
let regions = _.groupBy(region_events, 'regionId');
regions = _.compact(
_.map(regions, region_events => {
let region_obj = _.head(region_events);
if (region_events && region_events.length > 1) {
region_obj.timeEnd = region_events[1].time;
region_obj.isRegion = true;
return region_obj;
} else {
if (region_events && region_events.length) {
// Don't change proper region object
if (!region_obj.time || !region_obj.timeEnd) {
// This is cut region
if (isStartOfRegion(region_obj)) {
region_obj.timeEnd = range.to.valueOf() - 1;
} else {
// Start time = null
region_obj.timeEnd = region_obj.time;
region_obj.time = range.from.valueOf() + 1;
}
region_obj.isRegion = true;
}
return region_obj;
}
}
}),
);
return regions;
}
function isStartOfRegion(event): boolean {
return event.id && event.id === event.regionId;
}
export function dedupAnnotations(annotations) {
let dedup = [];
// Split events by annotationId property existance
let events = _.partition(annotations, 'id');
let eventsById = _.groupBy(events[0], 'id');
dedup = _.map(eventsById, eventGroup => {
if (eventGroup.length > 1 && !_.every(eventGroup, isPanelAlert)) {
// Get first non-panel alert
return _.find(eventGroup, event => {
return event.eventType !== 'panel-alert';
});
} else {
return _.head(eventGroup);
}
});
dedup = _.concat(dedup, events[1]);
return dedup;
}
function isPanelAlert(event) {
return event.eventType === 'panel-alert';
}

View File

@ -0,0 +1,83 @@
import {makeRegions, dedupAnnotations} from '../events_processing';
describe('Annotations', () => {
describe('Annotations regions', () => {
let testAnnotations: any[];
beforeEach(() => {
testAnnotations = [
{id: 1, time: 1},
{id: 2, time: 2},
{id: 3, time: 3, regionId: 3},
{id: 4, time: 5, regionId: 3},
{id: 5, time: 4, regionId: 5},
{id: 6, time: 8, regionId: 5}
];
});
it('should convert single region events to regions', () => {
const range = {from: 0, to: 10};
const expectedAnnotations = [
{id: 3, regionId: 3, isRegion: true, time: 3, timeEnd: 5},
{id: 5, regionId: 5, isRegion: true, time: 4, timeEnd: 8},
{id: 1, time: 1},
{id: 2, time: 2}
];
let regions = makeRegions(testAnnotations, {range: range});
expect(regions).toEqual(expectedAnnotations);
});
it('should cut regions to current time range', () => {
const range = {from: 0, to: 8};
testAnnotations = [
{id: 5, time: 4, regionId: 5}
];
const expectedAnnotations = [
{id: 5, regionId: 5, isRegion: true, time: 4, timeEnd: 7}
];
let regions = makeRegions(testAnnotations, {range: range});
expect(regions).toEqual(expectedAnnotations);
});
});
describe('Annotations deduplication', () => {
it('should remove duplicated annotations', () => {
const testAnnotations = [
{id: 1, time: 1},
{id: 2, time: 2},
{id: 2, time: 2},
{id: 5, time: 5},
{id: 5, time: 5}
];
const expectedAnnotations = [
{id: 1, time: 1},
{id: 2, time: 2},
{id: 5, time: 5}
];
let deduplicated = dedupAnnotations(testAnnotations);
expect(deduplicated).toEqual(expectedAnnotations);
});
it('should leave non "panel-alert" event if present', () => {
const testAnnotations = [
{id: 1, time: 1},
{id: 2, time: 2},
{id: 2, time: 2, eventType: 'panel-alert'},
{id: 5, time: 5},
{id: 5, time: 5}
];
const expectedAnnotations = [
{id: 1, time: 1},
{id: 2, time: 2},
{id: 5, time: 5}
];
let deduplicated = dedupAnnotations(testAnnotations);
expect(deduplicated).toEqual(expectedAnnotations);
});
});
});

View File

@ -7,6 +7,7 @@ import {coreModule, JsonExplorer} from 'app/core/core';
const template = ` const template = `
<div class="query-troubleshooter" ng-if="ctrl.isOpen"> <div class="query-troubleshooter" ng-if="ctrl.isOpen">
<div class="query-troubleshooter__header"> <div class="query-troubleshooter__header">
<a class="pointer" ng-click="ctrl.toggleMocking()">Mock Response</a>
<a class="pointer" ng-click="ctrl.toggleExpand()" ng-hide="ctrl.allNodesExpanded"> <a class="pointer" ng-click="ctrl.toggleExpand()" ng-hide="ctrl.allNodesExpanded">
<i class="fa fa-plus-square-o"></i> Expand All <i class="fa fa-plus-square-o"></i> Expand All
</a> </a>
@ -15,10 +16,15 @@ const template = `
</a> </a>
<a class="pointer" clipboard-button="ctrl.getClipboardText()"><i class="fa fa-clipboard"></i> Copy to Clipboard</a> <a class="pointer" clipboard-button="ctrl.getClipboardText()"><i class="fa fa-clipboard"></i> Copy to Clipboard</a>
</div> </div>
<div class="query-troubleshooter__body"> <div class="query-troubleshooter__body" ng-hide="ctrl.isMocking">
<i class="fa fa-spinner fa-spin" ng-show="ctrl.isLoading"></i> <i class="fa fa-spinner fa-spin" ng-show="ctrl.isLoading"></i>
<div class="query-troubleshooter-json"></div> <div class="query-troubleshooter-json"></div>
</div> </div>
<div class="query-troubleshooter__body" ng-show="ctrl.isMocking">
<div class="gf-form p-l-1 gf-form--v-stretch">
<textarea class="gf-form-input" style="width: 95%" rows="10" ng-model="ctrl.mockedResponse" placeholder="JSON"></textarea>
</div>
</div>
</div> </div>
`; `;
@ -32,6 +38,8 @@ export class QueryTroubleshooterCtrl {
onRequestResponseEventListener: any; onRequestResponseEventListener: any;
hasError: boolean; hasError: boolean;
allNodesExpanded: boolean; allNodesExpanded: boolean;
isMocking: boolean;
mockedResponse: string;
jsonExplorer: JsonExplorer; jsonExplorer: JsonExplorer;
/** @ngInject **/ /** @ngInject **/
@ -51,6 +59,10 @@ export class QueryTroubleshooterCtrl {
appEvents.off('ds-request-error', this.onRequestErrorEventListener); appEvents.off('ds-request-error', this.onRequestErrorEventListener);
} }
toggleMocking() {
this.isMocking = !this.isMocking;
}
onRequestError(err) { onRequestError(err) {
// ignore if closed // ignore if closed
if (!this.isOpen) { if (!this.isOpen) {
@ -76,12 +88,29 @@ export class QueryTroubleshooterCtrl {
return ''; return '';
} }
handleMocking(data) {
var mockedData;
try {
mockedData = JSON.parse(this.mockedResponse);
} catch (err) {
appEvents.emit('alert-error', ['Failed to parse mocked response']);
return;
}
data.data = mockedData;
}
onRequestResponse(data) { onRequestResponse(data) {
// ignore if closed // ignore if closed
if (!this.isOpen) { if (!this.isOpen) {
return; return;
} }
if (this.isMocking) {
this.handleMocking(data);
return;
}
this.isLoading = false; this.isLoading = false;
data = _.cloneDeep(data); data = _.cloneDeep(data);

View File

@ -15,7 +15,7 @@ import * as flatten from 'app/core/utils/flatten';
import * as ticks from 'app/core/utils/ticks'; import * as ticks from 'app/core/utils/ticks';
import {impressions} from 'app/features/dashboard/impression_store'; import {impressions} from 'app/features/dashboard/impression_store';
import builtInPlugins from './built_in_plugins'; import builtInPlugins from './built_in_plugins';
import d3 from 'vendor/d3/d3'; import * as d3 from 'd3';
// rxjs // rxjs
import {Observable} from 'rxjs/Observable'; import {Observable} from 'rxjs/Observable';

View File

@ -1,5 +1,3 @@
import {describe, it, expect} from 'test/lib/common';
import {containsVariable, assignModelProperties} from '../variable'; import {containsVariable, assignModelProperties} from '../variable';
describe('containsVariable', function() { describe('containsVariable', function() {
@ -8,37 +6,37 @@ describe('containsVariable', function() {
it('should find it with $var syntax', function() { it('should find it with $var syntax', function() {
var contains = containsVariable('this.$test.filters', 'test'); var contains = containsVariable('this.$test.filters', 'test');
expect(contains).to.be(true); expect(contains).toBe(true);
}); });
it('should not find it if only part matches with $var syntax', function() { it('should not find it if only part matches with $var syntax', function() {
var contains = containsVariable('this.$serverDomain.filters', 'server'); var contains = containsVariable('this.$serverDomain.filters', 'server');
expect(contains).to.be(false); expect(contains).toBe(false);
}); });
it('should find it if it ends with variable and passing multiple test strings', function() { it('should find it if it ends with variable and passing multiple test strings', function() {
var contains = containsVariable('show field keys from $pgmetric', 'test string2', 'pgmetric'); var contains = containsVariable('show field keys from $pgmetric', 'test string2', 'pgmetric');
expect(contains).to.be(true); expect(contains).toBe(true);
}); });
it('should find it with [[var]] syntax', function() { it('should find it with [[var]] syntax', function() {
var contains = containsVariable('this.[[test]].filters', 'test'); var contains = containsVariable('this.[[test]].filters', 'test');
expect(contains).to.be(true); expect(contains).toBe(true);
}); });
it('should find it when part of segment', function() { it('should find it when part of segment', function() {
var contains = containsVariable('metrics.$env.$group-*', 'group'); var contains = containsVariable('metrics.$env.$group-*', 'group');
expect(contains).to.be(true); expect(contains).toBe(true);
}); });
it('should find it its the only thing', function() { it('should find it its the only thing', function() {
var contains = containsVariable('$env', 'env'); var contains = containsVariable('$env', 'env');
expect(contains).to.be(true); expect(contains).toBe(true);
}); });
it('should be able to pass in multiple test strings', function() { it('should be able to pass in multiple test strings', function() {
var contains = containsVariable('asd','asd2.$env', 'env'); var contains = containsVariable('asd','asd2.$env', 'env');
expect(contains).to.be(true); expect(contains).toBe(true);
}); });
}); });
@ -50,14 +48,14 @@ describe('assignModelProperties', function() {
it('only set properties defined in defaults', function() { it('only set properties defined in defaults', function() {
var target: any = {test: 'asd'}; var target: any = {test: 'asd'};
assignModelProperties(target, {propA: 1, propB: 2}, {propB: 0}); assignModelProperties(target, {propA: 1, propB: 2}, {propB: 0});
expect(target.propB).to.be(2); expect(target.propB).toBe(2);
expect(target.test).to.be('asd'); expect(target.test).toBe('asd');
}); });
it('use default value if not found on source', function() { it('use default value if not found on source', function() {
var target: any = {test: 'asd'}; var target: any = {test: 'asd'};
assignModelProperties(target, {propA: 1, propB: 2}, {propC: 10}); assignModelProperties(target, {propA: 1, propB: 2}, {propC: 10});
expect(target.propC).to.be(10); expect(target.propC).toBe(10);
}); });
}); });

View File

@ -1,8 +1,6 @@
///<reference path="../../../headers/common.d.ts" />
import _ from 'lodash'; import _ from 'lodash';
import TimeSeries from 'app/core/time_series2'; import TimeSeries from 'app/core/time_series2';
import {colors} from 'app/core/core'; import colors from 'app/core/utils/colors';
export class DataProcessor { export class DataProcessor {

View File

@ -381,7 +381,7 @@ function graphDirective($rootScope, timeSrv, popoverSrv, contextSrv) {
var haveSortOrder = sortOrder !== null || sortOrder !== undefined; var haveSortOrder = sortOrder !== null || sortOrder !== undefined;
if (panel.stack && haveSortBy && haveSortOrder) { if (panel.stack && haveSortBy && haveSortOrder) {
var desc = desc = panel.legend.sortDesc === true ? 1 : -1; var desc = desc = panel.legend.sortDesc === true ? -1 : 1;
series.sort((x, y) => { series.sort((x, y) => {
if (x.stats[sortBy] > y.stats[sortBy]) { if (x.stats[sortBy] > y.stats[sortBy]) {
return 1 * desc; return 1 * desc;

View File

@ -127,7 +127,7 @@ function ($, core) {
value: value, value: value,
hoverIndex: hoverIndex, hoverIndex: hoverIndex,
color: series.color, color: series.color,
label: series.label, label: series.aliasEscaped,
time: pointTime, time: pointTime,
distance: hoverDistance, distance: hoverDistance,
index: i index: i
@ -264,7 +264,7 @@ function ($, core) {
else if (item) { else if (item) {
series = seriesList[item.seriesIndex]; series = seriesList[item.seriesIndex];
group = '<div class="graph-tooltip-list-item"><div class="graph-tooltip-series-name">'; group = '<div class="graph-tooltip-list-item"><div class="graph-tooltip-series-name">';
group += '<i class="fa fa-minus" style="color:' + item.series.color +';"></i> ' + series.label + ':</div>'; group += '<i class="fa fa-minus" style="color:' + item.series.color +';"></i> ' + series.aliasEscaped + ':</div>';
if (panel.stack && panel.tooltip.value_type === 'individual') { if (panel.stack && panel.tooltip.value_type === 'individual') {
value = item.datapoint[1] - item.datapoint[2]; value = item.datapoint[1] - item.datapoint[2];

View File

@ -169,7 +169,7 @@ function (angular, _, $) {
html += '<i class="fa fa-minus pointer" style="color:' + series.color + '"></i>'; html += '<i class="fa fa-minus pointer" style="color:' + series.color + '"></i>';
html += '</div>'; html += '</div>';
html += '<a class="graph-legend-alias pointer" title="' + _.escape(series.label) + '">' + _.escape(series.label) + '</a>'; html += '<a class="graph-legend-alias pointer" title="' + series.aliasEscaped + '">' + series.aliasEscaped + '</a>';
if (panel.legend.values) { if (panel.legend.values) {
var avg = series.formatValue(series.stats.avg); var avg = series.formatValue(series.stats.avg);

View File

@ -1,7 +1,3 @@
///<reference path="../../../../headers/common.d.ts" />
import {describe, beforeEach, it, expect} from '../../../../../test/lib/common';
import {DataProcessor} from '../data_processor'; import {DataProcessor} from '../data_processor';
describe('Graph DataProcessor', function() { describe('Graph DataProcessor', function() {
@ -29,7 +25,7 @@ describe('Graph DataProcessor', function() {
}); });
it('Should automatically set xaxis mode to field', () => { it('Should automatically set xaxis mode to field', () => {
expect(panel.xaxis.mode).to.be('field'); expect(panel.xaxis.mode).toBe('field');
}); });
}); });
@ -48,16 +44,16 @@ describe('Graph DataProcessor', function() {
it('Should return all field names', () => { it('Should return all field names', () => {
var fields = processor.getDataFieldNames(dataList, false); var fields = processor.getDataFieldNames(dataList, false);
expect(fields).to.contain('hostname'); expect(fields).toContain('hostname');
expect(fields).to.contain('valueField'); expect(fields).toContain('valueField');
expect(fields).to.contain('nested.prop1'); expect(fields).toContain('nested.prop1');
expect(fields).to.contain('nested.value2'); expect(fields).toContain('nested.value2');
}); });
it('Should return all number fields', () => { it('Should return all number fields', () => {
var fields = processor.getDataFieldNames(dataList, true); var fields = processor.getDataFieldNames(dataList, true);
expect(fields).to.contain('valueField'); expect(fields).toContain('valueField');
expect(fields).to.contain('nested.value2'); expect(fields).toContain('nested.value2');
}); });
}); });
}); });

View File

@ -75,7 +75,7 @@ describe('grafanaGraph', function() {
alias: 'series1' alias: 'series1'
})); }));
ctx.data.push(new TimeSeries({ ctx.data.push(new TimeSeries({
datapoints: [[1,10],[2,20]], datapoints: [[10,1],[20,2]],
alias: 'series2' alias: 'series2'
})); }));
@ -112,10 +112,10 @@ describe('grafanaGraph', function() {
}); });
}); });
graphScenario('sort series as legend', (ctx) => { graphScenario('sorting stacked series as legend. disabled', (ctx) => {
describe("with sort as legend undefined", () => {
ctx.setup((ctrl) => { ctx.setup((ctrl) => {
ctrl.panel.legend.sort = undefined; ctrl.panel.legend.sort = undefined;
ctrl.panel.stack = false;
}); });
it("should not modify order of time series", () => { it("should not modify order of time series", () => {
@ -124,22 +124,24 @@ describe('grafanaGraph', function() {
}); });
}); });
describe("with sort as legend set to min. descending order", () => { graphScenario("sorting stacked series as legend. min descending order", (ctx) => {
ctx.setup((ctrl) => { ctx.setup(ctrl => {
ctrl.panel.legend.sort = 'min'; ctrl.panel.legend.sort = 'min';
ctrl.panel.legend.sortDesc = true; ctrl.panel.legend.sortDesc = true;
ctrl.panel.stack = true;
}); });
it("highest value should be first", () => { it("highest value should be first", () => {
expect(ctx.plotData[1].alias).to.be('series2'); expect(ctx.plotData[0].alias).to.be('series2');
expect(ctx.plotData[0].alias).to.be('series1'); expect(ctx.plotData[1].alias).to.be('series1');
}); });
}); });
describe("with sort as legend set to min. ascending order", () => { graphScenario("sorting stacked series as legend. min ascending order", (ctx) => {
ctx.setup((ctrl) => { ctx.setup((ctrl, data) => {
ctrl.panel.legend.sort = 'min'; ctrl.panel.legend.sort = 'min';
ctrl.panel.legend.sortDesc = true; ctrl.panel.legend.sortDesc = false;
ctrl.panel.stack = true;
}); });
it("lowest value should be first", () => { it("lowest value should be first", () => {
@ -148,16 +150,29 @@ describe('grafanaGraph', function() {
}); });
}); });
describe("with sort as legend set to current. ascending order", () => { graphScenario("sorting stacked series as legend. stacking disabled", (ctx) => {
ctx.setup((ctrl) => {
ctrl.panel.legend.sort = 'min';
ctrl.panel.legend.sortDesc = true;
ctrl.panel.stack = false;
});
it("highest value should be first", () => {
expect(ctx.plotData[0].alias).to.be('series1');
expect(ctx.plotData[1].alias).to.be('series2');
});
});
graphScenario("sorting stacked series as legend. current descending order", (ctx) => {
ctx.setup((ctrl) => { ctx.setup((ctrl) => {
ctrl.panel.legend.sort = 'current'; ctrl.panel.legend.sort = 'current';
ctrl.panel.legend.sortDesc = false; ctrl.panel.legend.sortDesc = true;
ctrl.panel.stack = true;
}); });
it("highest last value should be first", () => { it("highest last value should be first", () => {
expect(ctx.plotData[1].alias).to.be('series2'); expect(ctx.plotData[0].alias).to.be('series2');
expect(ctx.plotData[0].alias).to.be('series1'); expect(ctx.plotData[1].alias).to.be('series1');
});
}); });
}); });
@ -300,7 +315,7 @@ describe('grafanaGraph', function() {
}); });
it('should set barWidth', function() { it('should set barWidth', function() {
expect(ctx.plotOptions.series.bars.barWidth).to.be(10/1.5); expect(ctx.plotOptions.series.bars.barWidth).to.be(1/1.5);
}); });
}); });
@ -383,146 +398,4 @@ describe('grafanaGraph', function() {
}); });
}, 10); }, 10);
// graphScenario('when using flexible Y-Min and Y-Max settings', function(ctx) {
// describe('and Y-Min is <100 and Y-Max is >200 and values within range', function() {
// ctx.setup(function(ctrl, data) {
// ctrl.panel.yaxes[0].min = '<100';
// ctrl.panel.yaxes[0].max = '>200';
// data[0] = new TimeSeries({
// datapoints: [[120,10],[160,20]],
// alias: 'series1',
// });
// });
//
// it('should set min to 100 and max to 200', function() {
// expect(ctx.plotOptions.yaxes[0].min).to.be(100);
// expect(ctx.plotOptions.yaxes[0].max).to.be(200);
// });
// });
// describe('and Y-Min is <100 and Y-Max is >200 and values outside range', function() {
// ctx.setup(function(ctrl, data) {
// ctrl.panel.yaxes[0].min = '<100';
// ctrl.panel.yaxes[0].max = '>200';
// data[0] = new TimeSeries({
// datapoints: [[99,10],[201,20]],
// alias: 'series1',
// });
// });
//
// it('should set min to auto and max to auto', function() {
// expect(ctx.plotOptions.yaxes[0].min).to.be(null);
// expect(ctx.plotOptions.yaxes[0].max).to.be(null);
// });
// });
// describe('and Y-Min is =10.5 and Y-Max is =10.5', function() {
// ctx.setup(function(ctrl, data) {
// ctrl.panel.yaxes[0].min = '=10.5';
// ctrl.panel.yaxes[0].max = '=10.5';
// data[0] = new TimeSeries({
// datapoints: [[100,10],[120,20], [110,30]],
// alias: 'series1',
// });
// });
//
// it('should set min to last value + 10.5 and max to last value + 10.5', function() {
// expect(ctx.plotOptions.yaxes[0].min).to.be(99.5);
// expect(ctx.plotOptions.yaxes[0].max).to.be(120.5);
// });
// });
// describe('and Y-Min is ~10.5 and Y-Max is ~10.5', function() {
// ctx.setup(function(ctrl, data) {
// ctrl.panel.yaxes[0].min = '~10.5';
// ctrl.panel.yaxes[0].max = '~10.5';
// data[0] = new TimeSeries({
// datapoints: [[102,10],[104,20], [110,30]], //Also checks precision
// alias: 'series1',
// });
// });
//
// it('should set min to average value + 10.5 and max to average value + 10.5', function() {
// expect(ctx.plotOptions.yaxes[0].min).to.be(94.8);
// expect(ctx.plotOptions.yaxes[0].max).to.be(115.8);
// });
// });
// });
// graphScenario('when using regular Y-Min and Y-Max settings', function(ctx) {
// describe('and Y-Min is 100 and Y-Max is 200', function() {
// ctx.setup(function(ctrl, data) {
// ctrl.panel.yaxes[0].min = '100';
// ctrl.panel.yaxes[0].max = '200';
// data[0] = new TimeSeries({
// datapoints: [[120,10],[160,20]],
// alias: 'series1',
// });
// });
//
// it('should set min to 100 and max to 200', function() {
// expect(ctx.plotOptions.yaxes[0].min).to.be(100);
// expect(ctx.plotOptions.yaxes[0].max).to.be(200);
// });
// });
// describe('and Y-Min is 0 and Y-Max is 0', function() {
// ctx.setup(function(ctrl, data) {
// ctrl.panel.yaxes[0].min = '0';
// ctrl.panel.yaxes[0].max = '0';
// data[0] = new TimeSeries({
// datapoints: [[120,10],[160,20]],
// alias: 'series1',
// });
// });
//
// it('should set min to 0 and max to 0', function() {
// expect(ctx.plotOptions.yaxes[0].min).to.be(0);
// expect(ctx.plotOptions.yaxes[0].max).to.be(0);
// });
// });
// describe('and negative values used', function() {
// ctx.setup(function(ctrl, data) {
// ctrl.panel.yaxes[0].min = '-10';
// ctrl.panel.yaxes[0].max = '-13.14';
// data[0] = new TimeSeries({
// datapoints: [[120,10],[160,20]],
// alias: 'series1',
// });
// });
//
// it('should set min and max to negative', function() {
// expect(ctx.plotOptions.yaxes[0].min).to.be(-10);
// expect(ctx.plotOptions.yaxes[0].max).to.be(-13.14);
// });
// });
// });
// graphScenario('when using Y-Min and Y-Max settings stored as number', function(ctx) {
// describe('and Y-Min is 0 and Y-Max is 100', function() {
// ctx.setup(function(ctrl, data) {
// ctrl.panel.yaxes[0].min = 0;
// ctrl.panel.yaxes[0].max = 100;
// data[0] = new TimeSeries({
// datapoints: [[120,10],[160,20]],
// alias: 'series1',
// });
// });
//
// it('should set min to 0 and max to 100', function() {
// expect(ctx.plotOptions.yaxes[0].min).to.be(0);
// expect(ctx.plotOptions.yaxes[0].max).to.be(100);
// });
// });
// describe('and Y-Min is -100 and Y-Max is -10.5', function() {
// ctx.setup(function(ctrl, data) {
// ctrl.panel.yaxes[0].min = -100;
// ctrl.panel.yaxes[0].max = -10.5;
// data[0] = new TimeSeries({
// datapoints: [[120,10],[160,20]],
// alias: 'series1',
// });
// });
//
// it('should set min to -100 and max to -10.5', function() {
// expect(ctx.plotOptions.yaxes[0].min).to.be(-100);
// expect(ctx.plotOptions.yaxes[0].max).to.be(-10.5);
// });
// });
// });
}); });

View File

@ -1,7 +1,3 @@
///<reference path="../../../../headers/common.d.ts" />
import { describe, beforeEach, it, expect } from '../../../../../test/lib/common';
import { convertValuesToHistogram, getSeriesValues } from '../histogram'; import { convertValuesToHistogram, getSeriesValues } from '../histogram';
describe('Graph Histogam Converter', function () { describe('Graph Histogam Converter', function () {
@ -21,7 +17,7 @@ describe('Graph Histogam Converter', function () {
]; ];
let histogram = convertValuesToHistogram(values, bucketSize); let histogram = convertValuesToHistogram(values, bucketSize);
expect(histogram).to.eql(expected); expect(histogram).toMatchObject(expected);
}); });
it('Should not add empty buckets', () => { it('Should not add empty buckets', () => {
@ -31,7 +27,7 @@ describe('Graph Histogam Converter', function () {
]; ];
let histogram = convertValuesToHistogram(values, bucketSize); let histogram = convertValuesToHistogram(values, bucketSize);
expect(histogram).to.eql(expected); expect(histogram).toMatchObject(expected);
}); });
}); });
@ -50,7 +46,7 @@ describe('Graph Histogam Converter', function () {
let expected = [1, 2, 10, 11, 17, 20, 29]; let expected = [1, 2, 10, 11, 17, 20, 29];
let values = getSeriesValues(data); let values = getSeriesValues(data);
expect(values).to.eql(expected); expect(values).toMatchObject(expected);
}); });
it('Should skip null values', () => { it('Should skip null values', () => {
@ -59,7 +55,7 @@ describe('Graph Histogam Converter', function () {
let expected = [1, 2, 10, 11, 17, 20, 29]; let expected = [1, 2, 10, 11, 17, 20, 29];
let values = getSeriesValues(data); let values = getSeriesValues(data);
expect(values).to.eql(expected); expect(values).toMatchObject(expected);
}); });
}); });
}); });

View File

@ -1,9 +1,10 @@
import angular from 'angular'; import angular from 'angular';
import _ from 'lodash'; import _ from 'lodash';
import $ from 'jquery'; import $ from 'jquery';
import d3 from 'vendor/d3/d3'; import * as d3 from 'd3';
import {contextSrv} from 'app/core/core'; import {contextSrv} from 'app/core/core';
import {tickStep} from 'app/core/utils/ticks'; import {tickStep} from 'app/core/utils/ticks';
import {getColorScale, getOpacityScale} from './color_scale';
let module = angular.module('grafana.directives'); let module = angular.module('grafana.directives');
@ -30,7 +31,7 @@ module.directive('colorLegend', function() {
if (panel.color.mode === 'spectrum') { if (panel.color.mode === 'spectrum') {
let colorScheme = _.find(ctrl.colorSchemes, {value: panel.color.colorScheme}); let colorScheme = _.find(ctrl.colorSchemes, {value: panel.color.colorScheme});
let colorScale = getColorScale(colorScheme, legendWidth); let colorScale = getColorScale(colorScheme, contextSrv.user.lightTheme, legendWidth);
drawSimpleColorLegend(elem, colorScale); drawSimpleColorLegend(elem, colorScale);
} else if (panel.color.mode === 'opacity') { } else if (panel.color.mode === 'opacity') {
let colorOptions = panel.color; let colorOptions = panel.color;
@ -93,7 +94,7 @@ function drawColorLegend(elem, colorScheme, rangeFrom, rangeTo, maxValue, minVal
let widthFactor = legendWidth / (rangeTo - rangeFrom); let widthFactor = legendWidth / (rangeTo - rangeFrom);
let valuesRange = d3.range(rangeFrom, rangeTo, rangeStep); let valuesRange = d3.range(rangeFrom, rangeTo, rangeStep);
let colorScale = getColorScale(colorScheme, maxValue, minValue); let colorScale = getColorScale(colorScheme, contextSrv.user.lightTheme, maxValue, minValue);
legend.selectAll(".heatmap-color-legend-rect") legend.selectAll(".heatmap-color-legend-rect")
.data(valuesRange) .data(valuesRange)
.enter().append("rect") .enter().append("rect")
@ -115,7 +116,10 @@ function drawOpacityLegend(elem, options, rangeFrom, rangeTo, maxValue, minValue
let legendWidth = Math.floor(legendElem.outerWidth()) - 30; let legendWidth = Math.floor(legendElem.outerWidth()) - 30;
let legendHeight = legendElem.attr("height"); let legendHeight = legendElem.attr("height");
let rangeStep = 10; let rangeStep = 1;
if (rangeTo - rangeFrom > legendWidth) {
rangeStep = Math.floor((rangeTo - rangeFrom) / legendWidth);
}
let widthFactor = legendWidth / (rangeTo - rangeFrom); let widthFactor = legendWidth / (rangeTo - rangeFrom);
let valuesRange = d3.range(rangeFrom, rangeTo, rangeStep); let valuesRange = d3.range(rangeFrom, rangeTo, rangeStep);
@ -228,31 +232,6 @@ function clearLegend(elem) {
legendElem.empty(); legendElem.empty();
} }
function getColorScale(colorScheme, maxValue, minValue = 0) {
let colorInterpolator = d3[colorScheme.value];
let colorScaleInverted = colorScheme.invert === 'always' ||
(colorScheme.invert === 'dark' && !contextSrv.user.lightTheme);
let start = colorScaleInverted ? maxValue : minValue;
let end = colorScaleInverted ? minValue : maxValue;
return d3.scaleSequential(colorInterpolator).domain([start, end]);
}
function getOpacityScale(options, maxValue, minValue = 0) {
let legendOpacityScale;
if (options.colorScale === 'linear') {
legendOpacityScale = d3.scaleLinear()
.domain([minValue, maxValue])
.range([0, 1]);
} else if (options.colorScale === 'sqrt') {
legendOpacityScale = d3.scalePow().exponent(options.exponent)
.domain([minValue, maxValue])
.range([0, 1]);
}
return legendOpacityScale;
}
function getSvgElemX(elem) { function getSvgElemX(elem) {
let svgElem = elem.get(0); let svgElem = elem.get(0);
if (svgElem && svgElem.x && svgElem.x.baseVal) { if (svgElem && svgElem.x && svgElem.x.baseVal) {

View File

@ -0,0 +1,27 @@
import * as d3 from 'd3';
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 start = colorScaleInverted ? maxValue : minValue;
let end = colorScaleInverted ? minValue : maxValue;
return d3.scaleSequential(colorInterpolator).domain([start, end]);
}
export function getOpacityScale(options, maxValue, minValue = 0) {
let legendOpacityScale;
if (options.colorScale === 'linear') {
legendOpacityScale = d3.scaleLinear()
.domain([minValue, maxValue])
.range([0, 1]);
} else if (options.colorScale === 'sqrt') {
legendOpacityScale = d3.scalePow().exponent(options.exponent)
.domain([minValue, maxValue])
.range([0, 1]);
}
return legendOpacityScale;
}

View File

@ -1,4 +1,4 @@
import d3 from 'vendor/d3/d3'; import * as d3 from 'd3';
import $ from 'jquery'; import $ from 'jquery';
import _ from 'lodash'; import _ from 'lodash';
import kbn from 'app/core/utils/kbn'; import kbn from 'app/core/utils/kbn';

View File

@ -1,12 +1,13 @@
import _ from 'lodash'; import _ from 'lodash';
import $ from 'jquery'; import $ from 'jquery';
import moment from 'moment'; import moment from 'moment';
import * as d3 from 'd3';
import kbn from 'app/core/utils/kbn'; import kbn from 'app/core/utils/kbn';
import {appEvents, contextSrv} from 'app/core/core'; import {appEvents, contextSrv} from 'app/core/core';
import {tickStep, getScaledDecimals, getFlotTickSize} from 'app/core/utils/ticks'; import {tickStep, getScaledDecimals, getFlotTickSize} from 'app/core/utils/ticks';
import d3 from 'vendor/d3/d3';
import {HeatmapTooltip} from './heatmap_tooltip'; import {HeatmapTooltip} from './heatmap_tooltip';
import {mergeZeroBuckets} from './heatmap_data_converter'; import {mergeZeroBuckets} from './heatmap_data_converter';
import {getColorScale, getOpacityScale} from './color_scale';
let MIN_CARD_SIZE = 1, let MIN_CARD_SIZE = 1,
CARD_PADDING = 1, CARD_PADDING = 1,
@ -386,8 +387,9 @@ export default function link(scope, elem, attrs, ctrl) {
let maxValue = panel.color.max || maxValueAuto; let maxValue = panel.color.max || maxValueAuto;
let minValue = panel.color.min || 0; let minValue = panel.color.min || 0;
colorScale = getColorScale(maxValue, minValue); let colorScheme = _.find(ctrl.colorSchemes, {value: panel.color.colorScheme});
setOpacityScale(maxValue); colorScale = getColorScale(colorScheme, contextSrv.user.lightTheme, maxValue, minValue);
opacityScale = getOpacityScale(panel.color, maxValue);
setCardSize(); setCardSize();
let cards = heatmap.selectAll(".heatmap-card").data(cardsData); let cards = heatmap.selectAll(".heatmap-card").data(cardsData);
@ -422,8 +424,8 @@ export default function link(scope, elem, attrs, ctrl) {
let strokeColor = d3.color(color).brighter(4); let strokeColor = d3.color(color).brighter(4);
let current_card = d3.select(event.target); let current_card = d3.select(event.target);
tooltip.originalFillColor = color; tooltip.originalFillColor = color;
current_card.style("fill", highlightColor) current_card.style("fill", highlightColor.toString())
.style("stroke", strokeColor) .style("stroke", strokeColor.toString())
.style("stroke-width", 1); .style("stroke-width", 1);
} }
@ -433,30 +435,6 @@ export default function link(scope, elem, attrs, ctrl) {
.style("stroke-width", 0); .style("stroke-width", 0);
} }
function getColorScale(maxValue, minValue = 0) {
let colorScheme = _.find(ctrl.colorSchemes, {value: panel.color.colorScheme});
let colorInterpolator = d3[colorScheme.value];
let colorScaleInverted = colorScheme.invert === 'always' ||
(colorScheme.invert === 'dark' && !contextSrv.user.lightTheme);
let start = colorScaleInverted ? maxValue : minValue;
let end = colorScaleInverted ? minValue : maxValue;
return d3.scaleSequential(colorInterpolator).domain([start, end]);
}
function setOpacityScale(maxValue) {
if (panel.color.colorScale === 'linear') {
opacityScale = d3.scaleLinear()
.domain([0, maxValue])
.range([0, 1]);
} else if (panel.color.colorScale === 'sqrt') {
opacityScale = d3.scalePow().exponent(panel.color.exponent)
.domain([0, maxValue])
.range([0, 1]);
}
}
function setCardSize() { function setCardSize() {
let xGridSize = Math.floor(xScale(data.xBucketSize) - xScale(0)); let xGridSize = Math.floor(xScale(data.xBucketSize) - xScale(0));
let yGridSize = Math.floor(yScale(yScale.invert(0) - data.yBucketSize)); let yGridSize = Math.floor(yScale(yScale.invert(0) - data.yBucketSize));

View File

@ -1,83 +0,0 @@
System.config({
defaultJSExtenions: true,
baseURL: 'public',
paths: {
'virtual-scroll': 'vendor/npm/virtual-scroll/src/index.js',
'mousetrap': 'vendor/npm/mousetrap/mousetrap.js',
'remarkable': 'vendor/npm/remarkable/dist/remarkable.js',
'tether': 'vendor/npm/tether/dist/js/tether.js',
'eventemitter3': 'vendor/npm/eventemitter3/index.js',
'tether-drop': 'vendor/npm/tether-drop/dist/js/drop.js',
'moment': 'vendor/moment.js',
"jquery": "vendor/jquery/dist/jquery.js",
'lodash-src': 'vendor/lodash/dist/lodash.js',
"lodash": 'app/core/lodash_extended.js',
"angular": "vendor/angular/angular.js",
"bootstrap": "vendor/bootstrap/bootstrap.js",
'angular-route': 'vendor/angular-route/angular-route.js',
'angular-sanitize': 'vendor/angular-sanitize/angular-sanitize.js',
"angular-ui": "vendor/angular-ui/ui-bootstrap-tpls.js",
"angular-strap": "vendor/angular-other/angular-strap.js",
"angular-dragdrop": "vendor/angular-native-dragdrop/draganddrop.js",
"angular-bindonce": "vendor/angular-bindonce/bindonce.js",
"spectrum": "vendor/spectrum.js",
"bootstrap-tagsinput": "vendor/tagsinput/bootstrap-tagsinput.js",
"jquery.flot": "vendor/flot/jquery.flot",
"jquery.flot.pie": "vendor/flot/jquery.flot.pie",
"jquery.flot.selection": "vendor/flot/jquery.flot.selection",
"jquery.flot.stack": "vendor/flot/jquery.flot.stack",
"jquery.flot.stackpercent": "vendor/flot/jquery.flot.stackpercent",
"jquery.flot.time": "vendor/flot/jquery.flot.time",
"jquery.flot.crosshair": "vendor/flot/jquery.flot.crosshair",
"jquery.flot.fillbelow": "vendor/flot/jquery.flot.fillbelow",
"jquery.flot.gauge": "vendor/flot/jquery.flot.gauge",
"d3": "vendor/d3/d3.js",
"jquery.flot.dashes": "vendor/flot/jquery.flot.dashes",
"twemoji": "vendor/npm/twemoji/2/twemoji.amd.js",
"ace": "vendor/npm/ace-builds/src-noconflict/ace",
},
packages: {
app: {
defaultExtension: 'js',
},
vendor: {
defaultExtension: 'js',
},
plugins: {
defaultExtension: 'js',
},
test: {
defaultExtension: 'js',
},
},
map: {
text: 'vendor/plugin-text/text.js',
css: 'app/core/utils/css_loader.js'
},
meta: {
'vendor/npm/virtual-scroll/src/indx.js': {
format: 'cjs',
exports: 'VirtualScroll',
},
'vendor/angular/angular.js': {
format: 'global',
deps: ['jquery'],
exports: 'angular',
},
'vendor/npm/eventemitter3/index.js': {
format: 'cjs',
exports: 'EventEmitter'
},
'vendor/npm/mousetrap/mousetrap.js': {
format: 'global',
exports: 'Mousetrap'
},
'vendor/npm/ace-builds/src-noconflict/ace.js': {
format: 'global',
exports: 'ace'
}
}
});

View File

@ -1,27 +0,0 @@
Copyright 2010-2016 Mike Bostock
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the author nor the names of contributors may be used to
endorse or promote products derived from this software without specific prior
written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,57 +0,0 @@
# D3: Data-Driven Documents
<a href="https://d3js.org"><img src="https://d3js.org/logo.svg" align="left" hspace="10" vspace="6"></a>
**D3** (or **D3.js**) is a JavaScript library for visualizing data using web standards. D3 helps you bring data to life using SVG, Canvas and HTML. D3 combines powerful visualization and interaction techniques with a data-driven approach to DOM manipulation, giving you the full capabilities of modern browsers and the freedom to design the right visual interface for your data.
## Resources
* [API Reference](https://github.com/d3/d3/blob/master/API.md)
* [Release Notes](https://github.com/d3/d3/releases)
* [Gallery](https://github.com/d3/d3/wiki/Gallery)
* [Examples](http://bl.ocks.org/mbostock)
* [Wiki](https://github.com/d3/d3/wiki)
## Installing
If you use npm, `npm install d3`. Otherwise, download the [latest release](https://github.com/d3/d3/releases/latest). The released bundle supports anonymous AMD, CommonJS, and vanilla environments. You can load directly from [d3js.org](https://d3js.org), [CDNJS](https://cdnjs.com/libraries/d3), or [unpkg](https://unpkg.com/d3/). For example:
```html
<script src="https://d3js.org/d3.v4.js"></script>
```
For the minified version:
```html
<script src="https://d3js.org/d3.v4.min.js"></script>
```
You can also use the standalone D3 microlibraries. For example, [d3-selection](https://github.com/d3/d3-selection):
```html
<script src="https://d3js.org/d3-selection.v1.js"></script>
```
D3 is written using [ES2015 modules](http://www.2ality.com/2014/09/es6-modules-final.html). Create a [custom bundle using Rollup](http://bl.ocks.org/mbostock/bb09af4c39c79cffcde4), Webpack, or your preferred bundler. To import D3 into an ES2015 application, either import specific symbols from specific D3 modules:
```js
import {scaleLinear} from "d3-scale";
```
Or import everything into a namespace (here, `d3`):
```js
import * as d3 from "d3";
```
In Node:
```js
var d3 = require("d3");
```
You can also require individual modules and combine them into a `d3` object using [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign):
```js
var d3 = Object.assign({}, require("d3-format"), require("d3-geo"), require("d3-geo-projection"));
```

File diff suppressed because one or more lines are too long

View File

@ -1,3 +0,0 @@
// Import main D3.js module and combine it with another
var d3 = Object.assign({}, require('./d3.v4.min.js'), require('./d3-scale-chromatic.min.js'));
module.exports = d3;

File diff suppressed because one or more lines are too long

View File

@ -12,37 +12,23 @@ function exit_if_fail {
cd /home/ubuntu/.go_workspace/src/github.com/grafana/grafana cd /home/ubuntu/.go_workspace/src/github.com/grafana/grafana
rm -rf node_modules
npm install -g yarn --quiet
yarn install --pure-lockfile --no-progress
exit_if_fail npm run test-ci
exit_if_fail npm run build
# publish code coverage
echo "Publishing javascript code coverage"
bash <(curl -s https://codecov.io/bash) -cF javascript
rm -rf coverage
# npm install -g codecov
# codecov
# cat ./coverage/lcov.info | node ./node_modules/coveralls/bin/coveralls.js
echo "running go fmt" echo "running go fmt"
exit_if_fail test -z "$(gofmt -s -l ./pkg | tee /dev/stderr)" exit_if_fail test -z "$(gofmt -s -l ./pkg | tee /dev/stderr)"
echo "running go vet" echo "running go vet"
exit_if_fail test -z "$(go vet ./pkg/... | tee /dev/stderr)" exit_if_fail test -z "$(go vet ./pkg/... | tee /dev/stderr)"
echo "building binaries" cd ~/dev/go/src/github.com/grafana/grafana
exit_if_fail go run build.go build echo "building backend with install to cache pkgs"
exit_if_fail time go install ./pkg/cmd/grafana-server
echo "running go test" echo "running go test"
set -e set -e
echo "" > coverage.txt echo "" > coverage.txt
for d in $(go list ./pkg/...); do time for d in $(go list ./pkg/...); do
exit_if_fail go test -race -coverprofile=profile.out -covermode=atomic $d exit_if_fail go test -coverprofile=profile.out -covermode=atomic $d
if [ -f profile.out ]; then if [ -f profile.out ]; then
cat profile.out >> coverage.txt cat profile.out >> coverage.txt
rm profile.out rm profile.out

25
scripts/circle-test-frontend.sh Executable file
View File

@ -0,0 +1,25 @@
#!/bin/bash
function exit_if_fail {
command=$@
echo "Executing '$command'"
eval $command
rc=$?
if [ $rc -ne 0 ]; then
echo "'$command' returned $rc."
exit $rc
fi
}
cd /home/ubuntu/.go_workspace/src/github.com/grafana/grafana
rm -rf node_modules
npm install -g yarn --quiet
yarn install --pure-lockfile --no-progress
exit_if_fail npm run test:coverage
exit_if_fail npm run build
# publish code coverage
echo "Publishing javascript code coverage"
bash <(curl -s https://codecov.io/bash) -cF javascript
rm -rf coverage

View File

@ -7,7 +7,7 @@ module.exports = function(grunt) {
'clean:release', 'clean:release',
'clean:build', 'clean:build',
'phantomjs', 'phantomjs',
'webpack:prod', 'exec:webpack',
]); ]);
}; };

View File

@ -19,6 +19,12 @@ module.exports = function(grunt) {
'no-only-tests' 'no-only-tests'
]); ]);
grunt.registerTask('precommit', [
'sasslint',
'exec:tslint',
'no-only-tests'
]);
grunt.registerTask('no-only-tests', function() { grunt.registerTask('no-only-tests', function() {
var files = grunt.file.expand('public/**/*_specs\.ts', 'public/**/*_specs\.js'); var files = grunt.file.expand('public/**/*_specs\.ts', 'public/**/*_specs\.js');

View File

@ -1,13 +1,14 @@
module.exports = function(config, grunt) { module.exports = function(config, grunt) {
'use strict' 'use strict';
var coverage = ''; var coverage = '';
if (config.coverage) { if (config.coverage) {
coverage = '--coverage'; coverage = '--coverage --maxWorkers 2';
} }
return { return {
tslint : "node ./node_modules/tslint/lib/tslint-cli.js -c tslint.json --project ./tsconfig.json", tslint: 'node ./node_modules/tslint/lib/tslint-cli.js -c tslint.json --project ./tsconfig.json',
jest : "node ./node_modules/jest-cli/bin/jest.js " + coverage, jest: 'node ./node_modules/jest-cli/bin/jest.js ' + coverage,
webpack: 'node ./node_modules/webpack/bin/webpack.js --config scripts/webpack/webpack.prod.js',
}; };
}; };

View File

@ -5,7 +5,7 @@ module.exports = function() {
'use strict'; 'use strict';
return { return {
options: { options: {
stats: !process.env.NODE_ENV || process.env.NODE_ENV === 'development' stats: false,
}, },
dev: dev, dev: dev,
prod: prod prod: prod

View File

@ -27,7 +27,10 @@ module.exports = merge(common, {
}, },
devServer: { devServer: {
stats: 'errors-only', noInfo: true,
stats: {
chunks: false,
},
}, },
plugins: [ plugins: [

View File

@ -1,11 +1,28 @@
Contributors to log15: Aaron L <aaron@bettercoder.net>
Alan Shreve <alan@inconshreveable.com>
- Aaron L Andy Walker <walkeraj@gmail.com>
- Alan Shreve Andy Watson <andrewmoorewatson@gmail.com>
- Chris Hines Carl Veazey <Carl_Veazey@cable.comcast.com>
- Ciaran Downey Chris Hines <github@cs-guy.com>
- Dmitry Chestnykh Christoph Hack <christoph@tux21b.org>
- Evan Shaw Ciaran Downey <me@ciarand.me>
- Péter Szilágyi Dmitry Chestnykh <dmitry@codingrobots.com>
- Trevor Gattis Evan Shaw <edsrzf@gmail.com>
- Vincent Vanackere Gonzalo Serrano <boikot@gmail.com>
Jeremy <jrbudnack@starkandwayne.com>
Jonathan Rudenberg <jonathan@titanous.com>
Kang Seong-Min <kang.seongmin@gmail.com>
Kevin Burke <kev@inburke.com>
Marc Abramowitz <marc@marc-abramowitz.com>
Nathan Baulch <nathan.baulch@gmail.com>
NotZippy <notzippy@gmail.com>
Péter Szilágyi <peterke@gmail.com>
Robert Egorov <robert.egorov@gmail.com>
Robert Starbuck <robstarbuck@gmail.com>
Robert Zaremba <robert.zaremba@scale-it.pl>
Sean Chittenden <sean@chittenden.org>
Spencer Nelson <s@spenczar.com>
Tomasz Grodzki <tg@users.noreply.github.com>
Trevor Gattis <github@trevorgattis.com>
Vincent Vanackere <vincent.vanackere@gmail.com>
Will McGovern <will@brkt.com>

View File

@ -73,5 +73,12 @@ srvlog := log.New(log.Ctx{"module": "app/server"})
srvlog.Warn("abnormal conn rate", log.Ctx{"rate": curRate, "low": lowRate, "high": highRate}) srvlog.Warn("abnormal conn rate", log.Ctx{"rate": curRate, "low": lowRate, "high": highRate})
``` ```
### Regenerating the CONTRIBUTORS file
```
go get -u github.com/kevinburke/write_mailmap
write_mailmap > CONTRIBUTORS
```
## License ## License
Apache Apache

View File

@ -23,7 +23,7 @@ To get started, you'll want to import the library:
Now you're ready to start logging: Now you're ready to start logging:
func main() { func main() {
log.Info("Program starting", "args", os.Args()) log.Info("Program starting", "args", os.Args)
} }

View File

@ -18,6 +18,7 @@ const (
termMsgJust = 40 termMsgJust = 40
) )
// Format is the interface implemented by StreamHandler formatters.
type Format interface { type Format interface {
Format(r *Record) []byte Format(r *Record) []byte
} }
@ -147,7 +148,7 @@ func JsonFormatEx(pretty, lineSeparated bool) Format {
if !ok { if !ok {
props[errorKey] = fmt.Sprintf("%+v is not a string key", r.Ctx[i]) props[errorKey] = fmt.Sprintf("%+v is not a string key", r.Ctx[i])
} }
props[k] = formatJsonValue(r.Ctx[i+1]) props[k] = formatJSONValue(r.Ctx[i+1])
} }
b, err := jsonMarshal(props) b, err := jsonMarshal(props)
@ -192,7 +193,7 @@ func formatShared(value interface{}) (result interface{}) {
} }
} }
func formatJsonValue(value interface{}) interface{} { func formatJSONValue(value interface{}) interface{} {
value = formatShared(value) value = formatShared(value)
switch value.(type) { switch value.(type) {
case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string: case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string:

View File

@ -11,8 +11,8 @@ import (
"github.com/go-stack/stack" "github.com/go-stack/stack"
) )
// A Logger prints its log records by writing to a Handler. // Handler interface defines where and how log records are written.
// The Handler interface defines where and how log records are written. // A logger prints its log records by writing to a Handler.
// Handlers are composable, providing you great flexibility in combining // Handlers are composable, providing you great flexibility in combining
// them to achieve the logging structure that suits your applications. // them to achieve the logging structure that suits your applications.
type Handler interface { type Handler interface {
@ -188,7 +188,7 @@ func LvlFilterHandler(maxLvl Lvl, h Handler) Handler {
}, h) }, h)
} }
// A MultiHandler dispatches any write to each of its handlers. // MultiHandler dispatches any write to each of its handlers.
// This is useful for writing different types of log information // This is useful for writing different types of log information
// to different locations. For example, to log to a file and // to different locations. For example, to log to a file and
// standard error: // standard error:
@ -207,7 +207,7 @@ func MultiHandler(hs ...Handler) Handler {
}) })
} }
// A FailoverHandler writes all log records to the first handler // FailoverHandler writes all log records to the first handler
// specified, but will failover and write to the second handler if // specified, but will failover and write to the second handler if
// the first handler has failed, and so on for all handlers specified. // the first handler has failed, and so on for all handlers specified.
// For example you might want to log to a network socket, but failover // For example you might want to log to a network socket, but failover
@ -229,11 +229,9 @@ func FailoverHandler(hs ...Handler) Handler {
err = h.Log(r) err = h.Log(r)
if err == nil { if err == nil {
return nil return nil
} else { }
r.Ctx = append(r.Ctx, fmt.Sprintf("failover_err_%d", i), err) r.Ctx = append(r.Ctx, fmt.Sprintf("failover_err_%d", i), err)
} }
}
return err return err
}) })
} }
@ -315,13 +313,12 @@ func evaluateLazy(lz Lazy) (interface{}, error) {
results := value.Call([]reflect.Value{}) results := value.Call([]reflect.Value{})
if len(results) == 1 { if len(results) == 1 {
return results[0].Interface(), nil return results[0].Interface(), nil
} else { }
values := make([]interface{}, len(results)) values := make([]interface{}, len(results))
for i, v := range results { for i, v := range results {
values[i] = v.Interface() values[i] = v.Interface()
} }
return values, nil return values, nil
}
} }
// DiscardHandler reports success for all writes but does nothing. // DiscardHandler reports success for all writes but does nothing.
@ -333,7 +330,7 @@ func DiscardHandler() Handler {
}) })
} }
// The Must object provides the following Handler creation functions // Must object provides the following Handler creation functions
// which instead of returning an error parameter only return a Handler // which instead of returning an error parameter only return a Handler
// and panic on failure: FileHandler, NetHandler, SyslogHandler, SyslogNetHandler // and panic on failure: FileHandler, NetHandler, SyslogHandler, SyslogNetHandler
var Must muster var Must muster

View File

@ -12,8 +12,10 @@ const lvlKey = "lvl"
const msgKey = "msg" const msgKey = "msg"
const errorKey = "LOG15_ERROR" const errorKey = "LOG15_ERROR"
// Lvl is a type for predefined log levels.
type Lvl int type Lvl int
// List of predefined log Levels
const ( const (
LvlCrit Lvl = iota LvlCrit Lvl = iota
LvlError LvlError
@ -40,7 +42,7 @@ func (l Lvl) String() string {
} }
} }
// Returns the appropriate Lvl from a string name. // LvlFromString returns the appropriate Lvl from a string name.
// Useful for parsing command line args and configuration files. // Useful for parsing command line args and configuration files.
func LvlFromString(lvlString string) (Lvl, error) { func LvlFromString(lvlString string) (Lvl, error) {
switch lvlString { switch lvlString {
@ -69,6 +71,7 @@ type Record struct {
KeyNames RecordKeyNames KeyNames RecordKeyNames
} }
// RecordKeyNames are the predefined names of the log props used by the Logger interface.
type RecordKeyNames struct { type RecordKeyNames struct {
Time string Time string
Msg string Msg string

View File

@ -3,10 +3,11 @@ package log15
import ( import (
"os" "os"
"github.com/inconshreveable/log15/term"
"github.com/mattn/go-colorable" "github.com/mattn/go-colorable"
isatty "github.com/mattn/go-isatty"
) )
// Predefined handlers
var ( var (
root *logger root *logger
StdoutHandler = StreamHandler(os.Stdout, LogfmtFormat()) StdoutHandler = StreamHandler(os.Stdout, LogfmtFormat())
@ -14,11 +15,11 @@ var (
) )
func init() { func init() {
if term.IsTty(os.Stdout.Fd()) { if isatty.IsTerminal(os.Stdout.Fd()) {
StdoutHandler = StreamHandler(colorable.NewColorableStdout(), TerminalFormat()) StdoutHandler = StreamHandler(colorable.NewColorableStdout(), TerminalFormat())
} }
if term.IsTty(os.Stderr.Fd()) { if isatty.IsTerminal(os.Stderr.Fd()) {
StderrHandler = StreamHandler(colorable.NewColorableStderr(), TerminalFormat()) StderrHandler = StreamHandler(colorable.NewColorableStderr(), TerminalFormat())
} }

View File

@ -1,5 +1,10 @@
# go-isatty # go-isatty
[![Godoc Reference](https://godoc.org/github.com/mattn/go-isatty?status.svg)](http://godoc.org/github.com/mattn/go-isatty)
[![Build Status](https://travis-ci.org/mattn/go-isatty.svg?branch=master)](https://travis-ci.org/mattn/go-isatty)
[![Coverage Status](https://coveralls.io/repos/github/mattn/go-isatty/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-isatty?branch=master)
[![Go Report Card](https://goreportcard.com/badge/mattn/go-isatty)](https://goreportcard.com/report/mattn/go-isatty)
isatty for golang isatty for golang
## Usage ## Usage
@ -16,6 +21,8 @@ import (
func main() { func main() {
if isatty.IsTerminal(os.Stdout.Fd()) { if isatty.IsTerminal(os.Stdout.Fd()) {
fmt.Println("Is Terminal") fmt.Println("Is Terminal")
} else if isatty.IsCygwinTerminal(os.Stdout.Fd()) {
fmt.Println("Is Cygwin/MSYS2 Terminal")
} else { } else {
fmt.Println("Is Not Terminal") fmt.Println("Is Not Terminal")
} }
@ -28,10 +35,16 @@ func main() {
$ go get github.com/mattn/go-isatty $ go get github.com/mattn/go-isatty
``` ```
# License ## License
MIT MIT
# Author ## Author
Yasuhiro Matsumoto (a.k.a mattn) Yasuhiro Matsumoto (a.k.a mattn)
## Thanks
* k-takata: base idea for IsCygwinTerminal
https://github.com/k-takata/go-iscygpty

View File

@ -7,3 +7,9 @@ package isatty
func IsTerminal(fd uintptr) bool { func IsTerminal(fd uintptr) bool {
return false return false
} }
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
// terminal. This is also always false on this environment.
func IsCygwinTerminal(fd uintptr) bool {
return false
}

View File

@ -1,4 +1,4 @@
// +build darwin freebsd openbsd netbsd // +build darwin freebsd openbsd netbsd dragonfly
// +build !appengine // +build !appengine
package isatty package isatty

View File

@ -1,5 +1,5 @@
// +build linux // +build linux
// +build !appengine // +build !appengine,!ppc64,!ppc64le
package isatty package isatty

View File

@ -0,0 +1,19 @@
// +build linux
// +build ppc64 ppc64le
package isatty
import (
"unsafe"
syscall "golang.org/x/sys/unix"
)
const ioctlReadTermios = syscall.TCGETS
// IsTerminal return true if the file descriptor is terminal.
func IsTerminal(fd uintptr) bool {
var termios syscall.Termios
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
return err == 0
}

10
vendor/github.com/mattn/go-isatty/isatty_others.go generated vendored Normal file
View File

@ -0,0 +1,10 @@
// +build !windows
// +build !appengine
package isatty
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
// terminal. This is also always false on this environment.
func IsCygwinTerminal(fd uintptr) bool {
return false
}

View File

@ -4,12 +4,30 @@
package isatty package isatty
import ( import (
"strings"
"syscall" "syscall"
"unicode/utf16"
"unsafe" "unsafe"
) )
var kernel32 = syscall.NewLazyDLL("kernel32.dll") const (
var procGetConsoleMode = kernel32.NewProc("GetConsoleMode") fileNameInfo uintptr = 2
fileTypePipe = 3
)
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx")
procGetFileType = kernel32.NewProc("GetFileType")
)
func init() {
// Check if GetFileInformationByHandleEx is available.
if procGetFileInformationByHandleEx.Find() != nil {
procGetFileInformationByHandleEx = nil
}
}
// IsTerminal return true if the file descriptor is terminal. // IsTerminal return true if the file descriptor is terminal.
func IsTerminal(fd uintptr) bool { func IsTerminal(fd uintptr) bool {
@ -17,3 +35,60 @@ func IsTerminal(fd uintptr) bool {
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0) r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0)
return r != 0 && e == 0 return r != 0 && e == 0
} }
// Check pipe name is used for cygwin/msys2 pty.
// Cygwin/MSYS2 PTY has a name like:
// \{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master
func isCygwinPipeName(name string) bool {
token := strings.Split(name, "-")
if len(token) < 5 {
return false
}
if token[0] != `\msys` && token[0] != `\cygwin` {
return false
}
if token[1] == "" {
return false
}
if !strings.HasPrefix(token[2], "pty") {
return false
}
if token[3] != `from` && token[3] != `to` {
return false
}
if token[4] != "master" {
return false
}
return true
}
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
// terminal.
func IsCygwinTerminal(fd uintptr) bool {
if procGetFileInformationByHandleEx == nil {
return false
}
// Cygwin/msys's pty is a pipe.
ft, _, e := syscall.Syscall(procGetFileType.Addr(), 1, fd, 0, 0)
if ft != fileTypePipe || e != 0 {
return false
}
var buf [2 + syscall.MAX_PATH]uint16
r, _, e := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(),
4, fd, fileNameInfo, uintptr(unsafe.Pointer(&buf)),
uintptr(len(buf)*2), 0, 0)
if r == 0 || e != 0 {
return false
}
l := *(*uint32)(unsafe.Pointer(&buf))
return isCygwinPipeName(string(utf16.Decode(buf[2 : 2+l/2])))
}

16
vendor/vendor.json vendored
View File

@ -443,10 +443,10 @@
"revisionTime": "2016-12-15T22:53:35Z" "revisionTime": "2016-12-15T22:53:35Z"
}, },
{ {
"checksumSHA1": "mrmfY0cVu7jvgoIuTRaR8yVVh/M=", "checksumSHA1": "W7WyVSrJNaQNQt2R9O4DxrK58cs=",
"path": "github.com/inconshreveable/log15", "path": "github.com/inconshreveable/log15",
"revision": "39bacc234bf1afd0b68573e95b45871f67ba2cd4", "revision": "0decfc6c20d9ca0ad143b0e89dcaa20f810b4fb3",
"revisionTime": "2017-02-16T22:56:31Z" "revisionTime": "2016-11-12T20:41:34Z"
}, },
{ {
"checksumSHA1": "oVIIInZXKkcRozJfuH2vWJsAS7s=", "checksumSHA1": "oVIIInZXKkcRozJfuH2vWJsAS7s=",
@ -468,25 +468,29 @@
}, },
{ {
"checksumSHA1": "jaCQF1par6Jl8g+V2Cgp0n/0wSc=", "checksumSHA1": "jaCQF1par6Jl8g+V2Cgp0n/0wSc=",
"origin": "github.com/grafana/grafana/vendor/github.com/lib/pq/hstore",
"path": "github.com/lib/pq/hstore", "path": "github.com/lib/pq/hstore",
"revision": "23da1db4f16d9658a86ae9b717c245fc078f10f1", "revision": "23da1db4f16d9658a86ae9b717c245fc078f10f1",
"revisionTime": "2017-09-18T17:50:43Z" "revisionTime": "2017-09-18T17:50:43Z"
}, },
{ {
"checksumSHA1": "mJHrY33tDs2MRhHt+XunkRF/5ek=", "checksumSHA1": "mJHrY33tDs2MRhHt+XunkRF/5ek=",
"origin": "github.com/grafana/grafana/vendor/github.com/lib/pq/listen_example",
"path": "github.com/lib/pq/listen_example", "path": "github.com/lib/pq/listen_example",
"revision": "23da1db4f16d9658a86ae9b717c245fc078f10f1", "revision": "23da1db4f16d9658a86ae9b717c245fc078f10f1",
"revisionTime": "2017-09-18T17:50:43Z" "revisionTime": "2017-09-18T17:50:43Z"
}, },
{ {
"checksumSHA1": "AU3fA8Sm33Vj9PBoRPSeYfxLRuE=", "checksumSHA1": "AU3fA8Sm33Vj9PBoRPSeYfxLRuE=",
"origin": "github.com/grafana/grafana/vendor/github.com/lib/pq/oid",
"path": "github.com/lib/pq/oid", "path": "github.com/lib/pq/oid",
"revision": "23da1db4f16d9658a86ae9b717c245fc078f10f1", "revision": "23da1db4f16d9658a86ae9b717c245fc078f10f1",
"revisionTime": "2017-09-18T17:50:43Z" "revisionTime": "2017-09-18T17:50:43Z"
}, },
{
"checksumSHA1": "y/A5iuvwjytQE2CqVuphQRXR2nI=",
"origin": "github.com/grafana/grafana/vendor/github.com/mattn/go-isatty",
"path": "github.com/mattn/go-isatty",
"revision": "a5cdd64afdee435007ee3e9f6ed4684af949d568",
"revisionTime": "2017-09-25T05:49:04Z"
},
{ {
"checksumSHA1": "bKMZjd2wPw13VwoE7mBeSv5djFA=", "checksumSHA1": "bKMZjd2wPw13VwoE7mBeSv5djFA=",
"path": "github.com/matttproud/golang_protobuf_extensions/pbutil", "path": "github.com/matttproud/golang_protobuf_extensions/pbutil",