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

View File

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

View File

@ -1,6 +1,7 @@
+++
title = "Data Sources"
type = "docs"
aliases = ["/datasources/overview/"]
[menu.docs]
name = "Data Sources"
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)
* **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
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": {
"^.+\\.tsx?$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
},
"moduleDirectories": ["<rootDir>node_modules", "<rootDir>/public"],
"moduleDirectories": ["<rootDir>/node_modules", "<rootDir>/public"],
"roots": [
"<rootDir>/public"
],

View File

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

View File

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

View File

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

View File

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

View File

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

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;
};
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(() => {

View File

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

View File

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

View File

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

View File

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

View File

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

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 {shallow} from 'enzyme';
import {PasswordStrength} from '../ui/PasswordStrength';
import {PasswordStrength} from '../components/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 _ 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 = {};

View File

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

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 = `
<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);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@ -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,10 +112,10 @@ describe('grafanaGraph', function() {
});
});
graphScenario('sort series as legend', (ctx) => {
describe("with sort as legend 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", () => {
@ -124,22 +124,24 @@ describe('grafanaGraph', function() {
});
});
describe("with sort as legend set to min. descending order", () => {
ctx.setup((ctrl) => {
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');
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) => {
graphScenario("sorting stacked series as legend. min ascending order", (ctx) => {
ctx.setup((ctrl, data) => {
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", () => {
@ -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) => {
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", () => {
expect(ctx.plotData[1].alias).to.be('series2');
expect(ctx.plotData[0].alias).to.be('series1');
});
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);
// });
// });
// });
});

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

View File

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

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 'lodash';
import kbn from 'app/core/utils/kbn';

View File

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

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
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
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:build',
'phantomjs',
'webpack:prod',
'exec:webpack',
]);
};

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
// +build linux
// +build !appengine
// +build !appengine,!ppc64,!ppc64le
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
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])))
}

16
vendor/vendor.json vendored
View File

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