Преглед на файлове

Merge branch 'master' into develop

Torkel Ödegaard преди 8 години
родител
ревизия
b44daaabf2
променени са 69 файла, в които са добавени 1283 реда и са изтрити 645 реда
  1. 0 10
      .hooks/pre-commit
  2. 4 3
      CHANGELOG.md
  3. 3 3
      circle.yml
  4. 1 0
      docs/sources/features/datasources/index.md
  5. 1 0
      docs/sources/guides/whats-new-in-v4-6.md
  6. 4 0
      docs/yarn.lock
  7. 1 1
      jest.config.js
  8. 12 9
      package.json
  9. 2 3
      pkg/api/http_server.go
  10. 2 2
      pkg/log/log.go
  11. 4 0
      pkg/middleware/util.go
  12. 1 1
      pkg/plugins/frontend_plugin.go
  13. 34 0
      pkg/plugins/frontend_plugin_test.go
  14. 4 1
      public/app/app.ts
  15. 1 1
      public/app/core/angular_wrappers.ts
  16. 0 0
      public/app/core/components/PasswordStrength.tsx
  17. 1 1
      public/app/core/components/colorpicker/ColorPalette.tsx
  18. 4 4
      public/app/core/components/colorpicker/ColorPickerPopover.tsx
  19. 1 1
      public/app/core/components/colorpicker/SpectrumPicker.tsx
  20. 1 2
      public/app/core/core.ts
  21. 10 0
      public/app/core/specs/ColorPalette.jest.tsx
  22. 1 1
      public/app/core/specs/PasswordStrength.jest.tsx
  23. 628 0
      public/app/core/specs/__snapshots__/ColorPalette.jest.tsx.snap
  24. 2 2
      public/app/core/time_series2.ts
  25. 1 79
      public/app/features/annotations/annotations_srv.ts
  26. 80 0
      public/app/features/annotations/events_processing.ts
  27. 83 0
      public/app/features/annotations/specs/annotations_srv_specs.jest.ts
  28. 30 1
      public/app/features/panel/query_troubleshooter.ts
  29. 1 1
      public/app/features/plugins/plugin_loader.ts
  30. 10 12
      public/app/features/templating/specs/variable.jest.ts
  31. 1 3
      public/app/plugins/panel/graph/data_processor.ts
  32. 1 1
      public/app/plugins/panel/graph/graph.ts
  33. 2 2
      public/app/plugins/panel/graph/graph_tooltip.js
  34. 1 1
      public/app/plugins/panel/graph/legend.js
  35. 7 11
      public/app/plugins/panel/graph/specs/data_processor.jest.ts
  36. 53 180
      public/app/plugins/panel/graph/specs/graph_specs.ts
  37. 4 8
      public/app/plugins/panel/graph/specs/histogram.jest.ts
  38. 8 29
      public/app/plugins/panel/heatmap/color_legend.ts
  39. 27 0
      public/app/plugins/panel/heatmap/color_scale.ts
  40. 1 1
      public/app/plugins/panel/heatmap/heatmap_tooltip.ts
  41. 7 29
      public/app/plugins/panel/heatmap/rendering.ts
  42. 0 83
      public/app/system.conf.js
  43. 0 27
      public/vendor/d3/LICENSE
  44. 0 57
      public/vendor/d3/README.md
  45. 0 1
      public/vendor/d3/d3-scale-chromatic.min.js
  46. 0 3
      public/vendor/d3/d3.js
  47. 0 1
      public/vendor/d3/d3.v4.min.js
  48. 5 19
      scripts/circle-test-backend.sh
  49. 25 0
      scripts/circle-test-frontend.sh
  50. 1 1
      scripts/grunt/build_task.js
  51. 6 0
      scripts/grunt/default_task.js
  52. 5 4
      scripts/grunt/options/exec.js
  53. 1 1
      scripts/grunt/options/webpack.js
  54. 4 1
      scripts/webpack/webpack.prod.js
  55. 28 11
      vendor/github.com/inconshreveable/log15/CONTRIBUTORS
  56. 7 0
      vendor/github.com/inconshreveable/log15/README.md
  57. 1 1
      vendor/github.com/inconshreveable/log15/doc.go
  58. 3 2
      vendor/github.com/inconshreveable/log15/format.go
  59. 11 14
      vendor/github.com/inconshreveable/log15/handler.go
  60. 4 1
      vendor/github.com/inconshreveable/log15/logger.go
  61. 4 3
      vendor/github.com/inconshreveable/log15/root.go
  62. 15 2
      vendor/github.com/mattn/go-isatty/README.md
  63. 6 0
      vendor/github.com/mattn/go-isatty/isatty_appengine.go
  64. 1 1
      vendor/github.com/mattn/go-isatty/isatty_bsd.go
  65. 1 1
      vendor/github.com/mattn/go-isatty/isatty_linux.go
  66. 19 0
      vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go
  67. 10 0
      vendor/github.com/mattn/go-isatty/isatty_others.go
  68. 77 2
      vendor/github.com/mattn/go-isatty/isatty_windows.go
  69. 10 6
      vendor/vendor.json

+ 0 - 10
.hooks/pre-commit

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

+ 4 - 3
CHANGELOG.md

@@ -12,15 +12,16 @@
 ## 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)
 
-* **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 
 * **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)
 * **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)
+* **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)
 

+ 3 - 3
circle.yml

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

+ 1 - 0
docs/sources/features/datasources/index.md

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

+ 1 - 0
docs/sources/guides/whats-new-in-v4-6.md

@@ -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)
 * **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)
+* **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
 * **Go**: Grafana is now built using golang 1.9

+ 4 - 0
docs/yarn.lock

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

+ 1 - 1
jest.config.js

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

+ 12 - 9
package.json

@@ -95,14 +95,15 @@
     "zone.js": "^0.7.2"
   },
   "scripts": {
-    "dev": "./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",
-    "build": "./node_modules/.bin/grunt build",
-    "test": "./node_modules/.bin/grunt test",
-    "test-ci": "./node_modules/.bin/grunt test --coverage=true",
-    "lint": "./node_modules/.bin/tslint -c tslint.json --project tsconfig.json --type-check",
-    "karma": "./node_modules/grunt-cli/bin/grunt karma:dev",
-    "jest": "./node_modules/jest-cli/bin/jest --notify --watch"
+    "dev": "node ./node_modules/.bin/webpack --progress --colors --config scripts/webpack/webpack.dev.js",
+    "watch": "node ./node_modules/.bin/webpack --progress --colors --watch --config scripts/webpack/webpack.dev.js",
+    "build": "node ./node_modules/.bin/grunt build",
+    "test": "node ./node_modules/.bin/grunt test",
+    "test:coverage": "node ./node_modules/.bin/grunt test --coverage=true",
+    "lint": "node ./node_modules/.bin/tslint -c tslint.json --project tsconfig.json --type-check",
+    "karma": "node ./node_modules/grunt-cli/bin/grunt karma:dev",
+    "jest": "node ./node_modules/jest-cli/bin/jest.js --notify --watch",
+    "precommit": "node ./node_modules/grunt-cli/bin/grunt precommit"
   },
   "license": "Apache-2.0",
   "dependencies": {
@@ -133,6 +134,8 @@
     "rxjs": "^5.4.3",
     "tether": "^1.4.0",
     "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"
   }
 }

+ 2 - 3
pkg/api/http_server.go

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

+ 2 - 2
pkg/log/log.go

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

+ 4 - 0
pkg/middleware/util.go

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

+ 1 - 1
pkg/plugins/frontend_plugin.go

@@ -40,7 +40,7 @@ func getPluginLogoUrl(pluginType, path, baseUrl string) string {
 }
 
 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.BaseUrl = app.BaseUrl
 

+ 34 - 0
pkg/plugins/frontend_plugin_test.go

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

+ 4 - 1
public/app/app.ts

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

+ 1 - 1
public/app/core/angular_wrappers.ts

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

+ 0 - 0
public/app/core/ui/PasswordStrength.tsx → public/app/core/components/PasswordStrength.tsx


+ 1 - 1
public/app/core/components/colorpicker/ColorPalette.tsx

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

+ 4 - 4
public/app/core/components/colorpicker/ColorPickerPopover.tsx

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

+ 1 - 1
public/app/core/components/colorpicker/SpectrumPicker.tsx

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

+ 1 - 2
public/app/core/core.ts

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

+ 10 - 0
public/app/core/specs/ColorPalette.jest.tsx

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

+ 1 - 1
public/app/core/specs/PasswordStrength.jest.tsx

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

+ 628 - 0
public/app/core/specs/__snapshots__/ColorPalette.jest.tsx.snap

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

+ 2 - 2
public/app/core/time_series2.ts

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

+ 1 - 79
public/app/features/annotations/annotations_srv.ts

@@ -3,6 +3,7 @@ import './editor_ctrl';
 import angular from 'angular';
 import _ from 'lodash';
 import coreModule from 'app/core/core_module';
+import {makeRegions, dedupAnnotations} from './events_processing';
 
 export class AnnotationsSrv {
   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);

+ 80 - 0
public/app/features/annotations/events_processing.ts

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

+ 83 - 0
public/app/features/annotations/specs/annotations_srv_specs.jest.ts

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

+ 30 - 1
public/app/features/panel/query_troubleshooter.ts

@@ -7,6 +7,7 @@ import {coreModule, JsonExplorer} from 'app/core/core';
 const template = `
 <div class="query-troubleshooter" ng-if="ctrl.isOpen">
   <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">
       <i class="fa fa-plus-square-o"></i> Expand All
     </a>
@@ -15,10 +16,15 @@ const template = `
     </a>
     <a class="pointer" clipboard-button="ctrl.getClipboardText()"><i class="fa fa-clipboard"></i> Copy to Clipboard</a>
   </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>
     <div class="query-troubleshooter-json"></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>
 `;
 
@@ -32,6 +38,8 @@ export class QueryTroubleshooterCtrl {
   onRequestResponseEventListener: any;
   hasError: boolean;
   allNodesExpanded: boolean;
+  isMocking: boolean;
+  mockedResponse: string;
   jsonExplorer: JsonExplorer;
 
   /** @ngInject **/
@@ -51,6 +59,10 @@ export class QueryTroubleshooterCtrl {
     appEvents.off('ds-request-error', this.onRequestErrorEventListener);
   }
 
+  toggleMocking() {
+    this.isMocking = !this.isMocking;
+  }
+
   onRequestError(err) {
     // ignore if closed
     if (!this.isOpen) {
@@ -76,12 +88,29 @@ export class QueryTroubleshooterCtrl {
     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) {
     // ignore if closed
     if (!this.isOpen) {
       return;
     }
 
+    if (this.isMocking) {
+      this.handleMocking(data);
+      return;
+    }
+
     this.isLoading = false;
     data = _.cloneDeep(data);
 

+ 1 - 1
public/app/features/plugins/plugin_loader.ts

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

+ 10 - 12
public/app/features/templating/specs/variable_specs.ts → public/app/features/templating/specs/variable.jest.ts

@@ -1,5 +1,3 @@
-import {describe, it, expect} from 'test/lib/common';
-
 import {containsVariable, assignModelProperties} from '../variable';
 
 describe('containsVariable', function() {
@@ -8,37 +6,37 @@ describe('containsVariable', function() {
 
     it('should find it with $var syntax', function() {
       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() {
       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() {
       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() {
       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() {
       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() {
       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() {
       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() {
     var target: any = {test: 'asd'};
     assignModelProperties(target, {propA: 1, propB: 2}, {propB: 0});
-    expect(target.propB).to.be(2);
-    expect(target.test).to.be('asd');
+    expect(target.propB).toBe(2);
+    expect(target.test).toBe('asd');
   });
 
   it('use default value if not found on source', function() {
     var target: any = {test: 'asd'};
     assignModelProperties(target, {propA: 1, propB: 2}, {propC: 10});
-    expect(target.propC).to.be(10);
+    expect(target.propC).toBe(10);
   });
 
 });

+ 1 - 3
public/app/plugins/panel/graph/data_processor.ts

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

+ 1 - 1
public/app/plugins/panel/graph/graph.ts

@@ -381,7 +381,7 @@ function graphDirective($rootScope, timeSrv, popoverSrv, contextSrv) {
         var haveSortOrder = sortOrder !== null || sortOrder !== undefined;
 
         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) => {
             if (x.stats[sortBy] > y.stats[sortBy]) {
               return 1 * desc;

+ 2 - 2
public/app/plugins/panel/graph/graph_tooltip.js

@@ -127,7 +127,7 @@ function ($, core) {
           value: value,
           hoverIndex: hoverIndex,
           color: series.color,
-          label: series.label,
+          label: series.aliasEscaped,
           time: pointTime,
           distance: hoverDistance,
           index: i
@@ -264,7 +264,7 @@ function ($, core) {
       else if (item) {
         series = seriesList[item.seriesIndex];
         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') {
           value = item.datapoint[1] - item.datapoint[2];

+ 1 - 1
public/app/plugins/panel/graph/legend.js

@@ -169,7 +169,7 @@ function (angular, _, $) {
             html += '<i class="fa fa-minus pointer" style="color:' + series.color + '"></i>';
             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) {
               var avg = series.formatValue(series.stats.avg);

+ 7 - 11
public/app/plugins/panel/graph/specs/data_processor_specs.ts → public/app/plugins/panel/graph/specs/data_processor.jest.ts

@@ -1,7 +1,3 @@
-///<reference path="../../../../headers/common.d.ts" />
-
-import {describe, beforeEach, it, expect} from '../../../../../test/lib/common';
-
 import {DataProcessor} from '../data_processor';
 
 describe('Graph DataProcessor', function() {
@@ -29,7 +25,7 @@ describe('Graph DataProcessor', function() {
     });
 
     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', () => {
       var fields = processor.getDataFieldNames(dataList, false);
-      expect(fields).to.contain('hostname');
-      expect(fields).to.contain('valueField');
-      expect(fields).to.contain('nested.prop1');
-      expect(fields).to.contain('nested.value2');
+      expect(fields).toContain('hostname');
+      expect(fields).toContain('valueField');
+      expect(fields).toContain('nested.prop1');
+      expect(fields).toContain('nested.value2');
     });
 
     it('Should return all number fields', () => {
       var fields = processor.getDataFieldNames(dataList, true);
-      expect(fields).to.contain('valueField');
-      expect(fields).to.contain('nested.value2');
+      expect(fields).toContain('valueField');
+      expect(fields).toContain('nested.value2');
     });
   });
 });

+ 53 - 180
public/app/plugins/panel/graph/specs/graph_specs.ts

@@ -75,7 +75,7 @@ describe('grafanaGraph', function() {
             alias: 'series1'
           }));
           ctx.data.push(new TimeSeries({
-            datapoints: [[1,10],[2,20]],
+            datapoints: [[10,1],[20,2]],
             alias: 'series2'
           }));
 
@@ -112,52 +112,67 @@ describe('grafanaGraph', function() {
     });
   });
 
-  graphScenario('sort series as legend', (ctx) => {
-    describe("with sort as legend undefined", () => {
-      ctx.setup((ctrl) => {
-        ctrl.panel.legend.sort = undefined;
-      });
+  graphScenario('sorting stacked series as legend. disabled', (ctx) => {
+    ctx.setup((ctrl) => {
+      ctrl.panel.legend.sort = undefined;
+      ctrl.panel.stack = false;
+    });
 
-      it("should not modify order of time series", () => {
-        expect(ctx.plotData[0].alias).to.be('series1');
-        expect(ctx.plotData[1].alias).to.be('series2');
-      });
+    it("should not modify order of time series", () => {
+      expect(ctx.plotData[0].alias).to.be('series1');
+      expect(ctx.plotData[1].alias).to.be('series2');
     });
+  });
 
-    describe("with sort as legend set to min. descending order", () => {
-      ctx.setup((ctrl) => {
-        ctrl.panel.legend.sort = 'min';
-        ctrl.panel.legend.sortDesc = true;
-      });
+  graphScenario("sorting stacked series as legend. min descending order", (ctx) => {
+    ctx.setup(ctrl => {
+      ctrl.panel.legend.sort = 'min';
+      ctrl.panel.legend.sortDesc = true;
+      ctrl.panel.stack = true;
+    });
 
-      it("highest value should be first", () => {
-        expect(ctx.plotData[1].alias).to.be('series2');
-        expect(ctx.plotData[0].alias).to.be('series1');
-      });
+    it("highest value should be first", () => {
+      expect(ctx.plotData[0].alias).to.be('series2');
+      expect(ctx.plotData[1].alias).to.be('series1');
     });
+  });
 
-    describe("with sort as legend set to min. ascending order", () => {
-      ctx.setup((ctrl) => {
-        ctrl.panel.legend.sort = 'min';
-        ctrl.panel.legend.sortDesc = true;
-      });
+  graphScenario("sorting stacked series as legend. min ascending order", (ctx) => {
+    ctx.setup((ctrl, data) => {
+      ctrl.panel.legend.sort = 'min';
+      ctrl.panel.legend.sortDesc = false;
+      ctrl.panel.stack = true;
+    });
 
-      it("lowest value should be first", () => {
-        expect(ctx.plotData[0].alias).to.be('series1');
-        expect(ctx.plotData[1].alias).to.be('series2');
-      });
+    it("lowest value should be first", () => {
+      expect(ctx.plotData[0].alias).to.be('series1');
+      expect(ctx.plotData[1].alias).to.be('series2');
     });
+  });
 
-    describe("with sort as legend set to current. ascending order", () => {
-      ctx.setup((ctrl) => {
-        ctrl.panel.legend.sort = 'current';
-        ctrl.panel.legend.sortDesc = false;
-      });
+  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 last value should be first", () => {
-        expect(ctx.plotData[1].alias).to.be('series2');
-        expect(ctx.plotData[0].alias).to.be('series1');
-      });
+    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) => {
+      ctrl.panel.legend.sort = 'current';
+      ctrl.panel.legend.sortDesc = true;
+      ctrl.panel.stack = true;
+    });
+
+    it("highest last value should be first", () => {
+      expect(ctx.plotData[0].alias).to.be('series2');
+      expect(ctx.plotData[1].alias).to.be('series1');
     });
   });
 
@@ -300,7 +315,7 @@ describe('grafanaGraph', 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);
-
-  // 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);
-  //     });
-  //   });
-  // });
 });

+ 4 - 8
public/app/plugins/panel/graph/specs/histogram_specs.ts → public/app/plugins/panel/graph/specs/histogram.jest.ts

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

+ 8 - 29
public/app/plugins/panel/heatmap/color_legend.ts

@@ -1,9 +1,10 @@
 import angular from 'angular';
 import _ from 'lodash';
 import $ from 'jquery';
-import d3 from 'vendor/d3/d3';
+import * as d3 from 'd3';
 import {contextSrv} from 'app/core/core';
 import {tickStep} from 'app/core/utils/ticks';
+import {getColorScale, getOpacityScale} from './color_scale';
 
 let module = angular.module('grafana.directives');
 
@@ -30,7 +31,7 @@ module.directive('colorLegend', function() {
 
         if (panel.color.mode === 'spectrum') {
           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);
         } else if (panel.color.mode === 'opacity') {
           let colorOptions = panel.color;
@@ -93,7 +94,7 @@ function drawColorLegend(elem, colorScheme, rangeFrom, rangeTo, maxValue, minVal
   let widthFactor = legendWidth / (rangeTo - rangeFrom);
   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")
     .data(valuesRange)
     .enter().append("rect")
@@ -115,7 +116,10 @@ function drawOpacityLegend(elem, options, rangeFrom, rangeTo, maxValue, minValue
   let legendWidth = Math.floor(legendElem.outerWidth()) - 30;
   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 valuesRange = d3.range(rangeFrom, rangeTo, rangeStep);
 
@@ -228,31 +232,6 @@ function clearLegend(elem) {
   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) {
   let svgElem = elem.get(0);
   if (svgElem && svgElem.x && svgElem.x.baseVal) {

+ 27 - 0
public/app/plugins/panel/heatmap/color_scale.ts

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

+ 1 - 1
public/app/plugins/panel/heatmap/heatmap_tooltip.ts

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

+ 7 - 29
public/app/plugins/panel/heatmap/rendering.ts

@@ -1,12 +1,13 @@
 import _ from 'lodash';
 import $ from 'jquery';
 import moment from 'moment';
+import * as d3 from 'd3';
 import kbn from 'app/core/utils/kbn';
 import {appEvents, contextSrv} from 'app/core/core';
 import {tickStep, getScaledDecimals, getFlotTickSize} from 'app/core/utils/ticks';
-import d3 from 'vendor/d3/d3';
 import {HeatmapTooltip} from './heatmap_tooltip';
 import {mergeZeroBuckets} from './heatmap_data_converter';
+import {getColorScale, getOpacityScale} from './color_scale';
 
 let MIN_CARD_SIZE = 1,
     CARD_PADDING = 1,
@@ -386,8 +387,9 @@ export default function link(scope, elem, attrs, ctrl) {
     let maxValue = panel.color.max || maxValueAuto;
     let minValue = panel.color.min || 0;
 
-    colorScale = getColorScale(maxValue, minValue);
-    setOpacityScale(maxValue);
+    let colorScheme = _.find(ctrl.colorSchemes, {value: panel.color.colorScheme});
+    colorScale = getColorScale(colorScheme, contextSrv.user.lightTheme,  maxValue, minValue);
+    opacityScale = getOpacityScale(panel.color, maxValue);
     setCardSize();
 
     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 current_card = d3.select(event.target);
     tooltip.originalFillColor = color;
-    current_card.style("fill", highlightColor)
-    .style("stroke", strokeColor)
+    current_card.style("fill", highlightColor.toString())
+    .style("stroke", strokeColor.toString())
     .style("stroke-width", 1);
   }
 
@@ -433,30 +435,6 @@ export default function link(scope, elem, attrs, ctrl) {
     .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() {
     let xGridSize = Math.floor(xScale(data.xBucketSize) - xScale(0));
     let yGridSize = Math.floor(yScale(yScale.invert(0) - data.yBucketSize));

+ 0 - 83
public/app/system.conf.js

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

+ 0 - 27
public/vendor/d3/LICENSE

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

+ 0 - 57
public/vendor/d3/README.md

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

Файловите разлики са ограничени, защото са твърде много
+ 0 - 1
public/vendor/d3/d3-scale-chromatic.min.js


+ 0 - 3
public/vendor/d3/d3.js

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

Файловите разлики са ограничени, защото са твърде много
+ 0 - 1
public/vendor/d3/d3.v4.min.js


+ 5 - 19
scripts/circle-test.sh → scripts/circle-test-backend.sh

@@ -12,37 +12,23 @@ function exit_if_fail {
 
 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"
 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 binaries"
-exit_if_fail go run build.go build
+cd ~/dev/go/src/github.com/grafana/grafana
+echo "building backend with install to cache pkgs"
+exit_if_fail time go install ./pkg/cmd/grafana-server
 
 echo "running go test"
 
 set -e
 echo "" > coverage.txt
 
-for d in $(go list ./pkg/...); do
-  exit_if_fail go test -race -coverprofile=profile.out -covermode=atomic $d
+time for d in $(go list ./pkg/...); do
+  exit_if_fail go test -coverprofile=profile.out -covermode=atomic $d
   if [ -f profile.out ]; then
     cat profile.out >> coverage.txt
     rm profile.out

+ 25 - 0
scripts/circle-test-frontend.sh

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

+ 1 - 1
scripts/grunt/build_task.js

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

+ 6 - 0
scripts/grunt/default_task.js

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

+ 5 - 4
scripts/grunt/options/exec.js

@@ -1,13 +1,14 @@
 module.exports = function(config, grunt) {
-  'use strict'
+  'use strict';
 
   var coverage = '';
   if (config.coverage) {
-    coverage = '--coverage';
+    coverage = '--coverage --maxWorkers 2';
   }
 
   return {
-    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,
+    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,
+    webpack: 'node ./node_modules/webpack/bin/webpack.js --config scripts/webpack/webpack.prod.js',
   };
 };

+ 1 - 1
scripts/grunt/options/webpack.js

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

+ 4 - 1
scripts/webpack/webpack.prod.js

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

+ 28 - 11
vendor/github.com/inconshreveable/log15/CONTRIBUTORS

@@ -1,11 +1,28 @@
-Contributors to log15:
-
-- Aaron L 
-- Alan Shreve 
-- Chris Hines 
-- Ciaran Downey 
-- Dmitry Chestnykh 
-- Evan Shaw 
-- Péter Szilágyi 
-- Trevor Gattis 
-- Vincent Vanackere 
+Aaron L <aaron@bettercoder.net>
+Alan Shreve <alan@inconshreveable.com>
+Andy Walker <walkeraj@gmail.com>
+Andy Watson <andrewmoorewatson@gmail.com>
+Carl Veazey <Carl_Veazey@cable.comcast.com>
+Chris Hines <github@cs-guy.com>
+Christoph Hack <christoph@tux21b.org>
+Ciaran Downey <me@ciarand.me>
+Dmitry Chestnykh <dmitry@codingrobots.com>
+Evan Shaw <edsrzf@gmail.com>
+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>

+ 7 - 0
vendor/github.com/inconshreveable/log15/README.md

@@ -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})
 ```
 
+### Regenerating the CONTRIBUTORS file
+
+```
+go get -u github.com/kevinburke/write_mailmap
+write_mailmap > CONTRIBUTORS
+```
+
 ## License
 Apache

+ 1 - 1
vendor/github.com/inconshreveable/log15/doc.go

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

+ 3 - 2
vendor/github.com/inconshreveable/log15/format.go

@@ -18,6 +18,7 @@ const (
 	termMsgJust    = 40
 )
 
+// Format  is the interface implemented by StreamHandler formatters.
 type Format interface {
 	Format(r *Record) []byte
 }
@@ -147,7 +148,7 @@ func JsonFormatEx(pretty, lineSeparated bool) Format {
 			if !ok {
 				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)
@@ -192,7 +193,7 @@ func formatShared(value interface{}) (result interface{}) {
 	}
 }
 
-func formatJsonValue(value interface{}) interface{} {
+func formatJSONValue(value interface{}) interface{} {
 	value = formatShared(value)
 	switch value.(type) {
 	case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string:

+ 11 - 14
vendor/github.com/inconshreveable/log15/handler.go

@@ -11,8 +11,8 @@ import (
 	"github.com/go-stack/stack"
 )
 
-// A Logger prints its log records by writing to a Handler.
-// The Handler interface defines where and how log records are written.
+// 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
 // them to achieve the logging structure that suits your applications.
 type Handler interface {
@@ -188,7 +188,7 @@ func LvlFilterHandler(maxLvl Lvl, h Handler) Handler {
 	}, 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
 // to different locations. For example, to log to a file and
 // 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
 // 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
@@ -229,11 +229,9 @@ func FailoverHandler(hs ...Handler) Handler {
 			err = h.Log(r)
 			if err == 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
 	})
 }
@@ -315,13 +313,12 @@ func evaluateLazy(lz Lazy) (interface{}, error) {
 	results := value.Call([]reflect.Value{})
 	if len(results) == 1 {
 		return results[0].Interface(), nil
-	} else {
-		values := make([]interface{}, len(results))
-		for i, v := range results {
-			values[i] = v.Interface()
-		}
-		return values, nil
 	}
+	values := make([]interface{}, len(results))
+	for i, v := range results {
+		values[i] = v.Interface()
+	}
+	return values, nil
 }
 
 // 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
 // and panic on failure: FileHandler, NetHandler, SyslogHandler, SyslogNetHandler
 var Must muster

+ 4 - 1
vendor/github.com/inconshreveable/log15/logger.go

@@ -12,8 +12,10 @@ const lvlKey = "lvl"
 const msgKey = "msg"
 const errorKey = "LOG15_ERROR"
 
+// Lvl is a type for predefined log levels.
 type Lvl int
 
+// List of predefined log Levels
 const (
 	LvlCrit Lvl = iota
 	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.
 func LvlFromString(lvlString string) (Lvl, error) {
 	switch lvlString {
@@ -69,6 +71,7 @@ type Record struct {
 	KeyNames RecordKeyNames
 }
 
+// RecordKeyNames are the predefined names of the log props used by the Logger interface.
 type RecordKeyNames struct {
 	Time string
 	Msg  string

+ 4 - 3
vendor/github.com/inconshreveable/log15/root.go

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

+ 15 - 2
vendor/github.com/mattn/go-isatty/README.md

@@ -1,5 +1,10 @@
 # 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
 
 ## Usage
@@ -16,6 +21,8 @@ import (
 func main() {
 	if isatty.IsTerminal(os.Stdout.Fd()) {
 		fmt.Println("Is Terminal")
+	} else if isatty.IsCygwinTerminal(os.Stdout.Fd()) {
+		fmt.Println("Is Cygwin/MSYS2 Terminal")
 	} else {
 		fmt.Println("Is Not Terminal")
 	}
@@ -28,10 +35,16 @@ func main() {
 $ go get github.com/mattn/go-isatty
 ```
 
-# License
+## License
 
 MIT
 
-# Author
+## Author
 
 Yasuhiro Matsumoto (a.k.a mattn)
+
+## Thanks
+
+* k-takata: base idea for IsCygwinTerminal
+
+    https://github.com/k-takata/go-iscygpty

+ 6 - 0
vendor/github.com/mattn/go-isatty/isatty_appengine.go

@@ -7,3 +7,9 @@ package isatty
 func IsTerminal(fd uintptr) bool {
 	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
+}

+ 1 - 1
vendor/github.com/mattn/go-isatty/isatty_bsd.go

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

+ 1 - 1
vendor/github.com/mattn/go-isatty/isatty_linux.go

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

+ 19 - 0
vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go

@@ -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 - 0
vendor/github.com/mattn/go-isatty/isatty_others.go

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

+ 77 - 2
vendor/github.com/mattn/go-isatty/isatty_windows.go

@@ -4,12 +4,30 @@
 package isatty
 
 import (
+	"strings"
 	"syscall"
+	"unicode/utf16"
 	"unsafe"
 )
 
-var kernel32 = syscall.NewLazyDLL("kernel32.dll")
-var procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
+const (
+	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.
 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)
 	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])))
+}

+ 10 - 6
vendor/vendor.json

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

Някои файлове не бяха показани, защото твърде много файлове са промени