diff --git a/CHANGELOG.md b/CHANGELOG.md index ef6c3ffc273..2c61d812773 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,20 @@ - UX changes to nav & side menu - New dashboard grid layout system -# 4.6.0 (unreleased) +# 4.6.0-beta2 (2017-10-17) + +## Fixes +* **ColorPicker**: Fix for color picker not showing [#9549](https://github.com/grafana/grafana/issues/9549) +* **Alerting**: Fix for broken test rule button in alert tab [#9539](https://github.com/grafana/grafana/issues/9539) +* **Cloudwatch**: Provide error message when failing to add cloudwatch datasource [#9534](https://github.com/grafana/grafana/pull/9534), thx [@mtanda](https://github.com/mtanda) +* **Cloudwatch**: Fix unused period parameter [#9536](https://github.com/grafana/grafana/pull/9536), thx [@mtanda](https://github.com/mtanda) +* **CSV Export**: Fix for broken CSV export [#9525](https://github.com/grafana/grafana/issues/9525) +* **Text panel**: Fixes issue with break lines in Firefox [#9491](https://github.com/grafana/grafana/issues/9491) + +# 4.6.0-beta1 (2017-10-13) ## New Features +* **Annotations**: Add support for creating annotations from graph panel [#8197](https://github.com/grafana/grafana/pull/8197) * **GCS**: Adds support for Google Cloud Storage [#8370](https://github.com/grafana/grafana/issues/8370) thx [@chuhlomin](https://github.com/chuhlomin) * **Prometheus**: Adds /metrics endpoint for exposing Grafana metrics. [#9187](https://github.com/grafana/grafana/pull/9187) * **Graph**: Add support for local formating in axis. [#1395](https://github.com/grafana/grafana/issues/1395), thx [@m0nhawk](https://github.com/m0nhawk) @@ -36,10 +47,11 @@ * **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) * **Kafka**: Add support for sending alert notifications to kafka [#7104](https://github.com/grafana/grafana/issues/7104), thx [@utkarshcmu](https://github.com/utkarshcmu) +* **Alerting**: add count_non_null as series reducer [#9516](https://github.com/grafana/grafana/issues/9516) ## Tech * **Go**: Grafana is now built using golang 1.9 -* **Webpack**: Changed from systemjs to webpack (see readme or building from source guide for new build instructions). Systemjs is still used to load plugins but now plugins can only import a limited set of dependencies. See [PLUGIN_DEV.md](https://github.com/grafana/grafana/blob/master/PLUGIN_DEV.md) for more details on how this can effect some plugins. +* **Webpack**: Changed from systemjs to webpack (see readme or building from source guide for new build instructions). Systemjs is still used to load plugins but now plugins can only import a limited set of dependencies. See [PLUGIN_DEV.md](https://github.com/grafana/grafana/blob/master/PLUGIN_DEV.md) for more details on how this can effect some plugins. # 4.5.2 (2017-09-22) diff --git a/PLUGIN_DEV.md b/PLUGIN_DEV.md index 1f672aa5a3d..9d831a95697 100644 --- a/PLUGIN_DEV.md +++ b/PLUGIN_DEV.md @@ -23,6 +23,6 @@ If you think we missed exposing a crucial lib or Grafana component let us know b ### Deprecated components -The angular directive `` is no deprecated (will still work for a version more) but we recommend plugin authors +The angular directive `` is now deprecated (will still work for a version more) but we recommend plugin authors to upgrade to new `` diff --git a/docs/sources/features/datasources/cloudwatch.md b/docs/sources/features/datasources/cloudwatch.md index 29da06bfb1e..1d98b2d16d1 100644 --- a/docs/sources/features/datasources/cloudwatch.md +++ b/docs/sources/features/datasources/cloudwatch.md @@ -29,7 +29,7 @@ Name | Description ------------ | ------------- *Name* | The data source name. This is how you refer to the data source in panels & queries. *Default* | Default data source means that it will be pre-selected for new panels. -*Credentials* profile name | Specify the name of the profile to use (if you use `~/aws/credentials` file), leave blank for default. +*Credentials* profile name | Specify the name of the profile to use (if you use `~/.aws/credentials` file), leave blank for default. *Default Region* | Used in query editor to set region (can be changed on per query basis) *Custom Metrics namespace* | Specify the CloudWatch namespace of Custom metrics *Assume Role Arn* | Specify the ARN of the role to assume diff --git a/package.json b/package.json index 910928c43a3..c5bb731cca7 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "company": "Grafana Labs" }, "name": "grafana", - "version": "4.6.0-beta1", + "version": "4.7.0-pre1", "repository": { "type": "git", "url": "http://github.com/grafana/grafana.git" diff --git a/packaging/publish/publish_testing.sh b/packaging/publish/publish_testing.sh index 17fabc5e136..276193ad63f 100755 --- a/packaging/publish/publish_testing.sh +++ b/packaging/publish/publish_testing.sh @@ -1,10 +1,10 @@ #! /usr/bin/env bash -deb_ver=4.5.0-beta1 -rpm_ver=4.5.0-beta1 +deb_ver=4.6.0-beta1 +rpm_ver=4.6.0-beta1 -# wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_${deb_ver}_amd64.deb +wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_${deb_ver}_amd64.deb -# package_cloud push grafana/testing/debian/jessie grafana_${deb_ver}_amd64.deb +package_cloud push grafana/testing/debian/jessie grafana_${deb_ver}_amd64.deb package_cloud push grafana/testing/debian/wheezy grafana_${deb_ver}_amd64.deb package_cloud push grafana/testing/debian/stretch grafana_${deb_ver}_amd64.deb diff --git a/pkg/services/alerting/conditions/reducer.go b/pkg/services/alerting/conditions/reducer.go index 6eaa21b958e..0a61c13fa12 100644 --- a/pkg/services/alerting/conditions/reducer.go +++ b/pkg/services/alerting/conditions/reducer.go @@ -141,6 +141,16 @@ func (s *SimpleReducer) Reduce(series *tsdb.TimeSeries) null.Float { break } } + case "count_non_null": + for _, v := range series.Points { + if v[0].Valid { + value++ + } + } + + if value > 0 { + allNull = false + } } if allNull { diff --git a/pkg/services/alerting/conditions/reducer_test.go b/pkg/services/alerting/conditions/reducer_test.go index f0147e9021a..866b574f59f 100644 --- a/pkg/services/alerting/conditions/reducer_test.go +++ b/pkg/services/alerting/conditions/reducer_test.go @@ -67,6 +67,35 @@ func TestSimpleReducer(t *testing.T) { So(reducer.Reduce(series).Valid, ShouldEqual, false) }) + Convey("count_non_null", func() { + Convey("with null values and real values", func() { + reducer := NewSimpleReducer("count_non_null") + series := &tsdb.TimeSeries{ + Name: "test time serie", + } + + series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1)) + series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2)) + series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(3), 3)) + series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(3), 4)) + + So(reducer.Reduce(series).Valid, ShouldEqual, true) + So(reducer.Reduce(series).Float64, ShouldEqual, 2) + }) + + Convey("with null values", func() { + reducer := NewSimpleReducer("count_non_null") + series := &tsdb.TimeSeries{ + Name: "test time serie", + } + + series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1)) + series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2)) + + So(reducer.Reduce(series).Valid, ShouldEqual, false) + }) + }) + Convey("avg of number values and null values should ignore nulls", func() { reducer := NewSimpleReducer("avg") series := &tsdb.TimeSeries{ diff --git a/pkg/services/sqlstore/annotation.go b/pkg/services/sqlstore/annotation.go index a2c5d80ac3a..d97db10f630 100644 --- a/pkg/services/sqlstore/annotation.go +++ b/pkg/services/sqlstore/annotation.go @@ -43,7 +43,7 @@ func (r *SqlAnnotationRepo) ensureTagsExist(sess *DBSession, tags []*models.Tag) var existingTag models.Tag // check if it exists - if exists, err := sess.Table("tag").Where("key=? AND value=?", tag.Key, tag.Value).Get(&existingTag); err != nil { + if exists, err := sess.Table("tag").Where("`key`=? AND `value`=?", tag.Key, tag.Value).Get(&existingTag); err != nil { return nil, err } else if exists { tag.Id = existingTag.Id diff --git a/pkg/tsdb/cloudwatch/metric_find_query.go b/pkg/tsdb/cloudwatch/metric_find_query.go index 3f4f7bea9ef..b1ce507d27c 100644 --- a/pkg/tsdb/cloudwatch/metric_find_query.go +++ b/pkg/tsdb/cloudwatch/metric_find_query.go @@ -38,7 +38,7 @@ var customMetricsDimensionsMap map[string]map[string]map[string]*CustomMetricsCa func init() { metricsMap = map[string][]string{ "AWS/ApiGateway": {"4XXError", "5XXError", "CacheHitCount", "CacheMissCount", "Count", "IntegrationLatency", "Latency"}, - "AWS/ApplicationELB": {"ActiveConnectionCount", "ClientTLSNegotiationErrorCount", "HealthyHostCount", "HTTPCode_ELB_4XX_Count", "HTTPCode_ELB_5XX_Count", "HTTPCode_Target_2XX_Count", "HTTPCode_Target_3XX_Count", "HTTPCode_Target_4XX_Count", "HTTPCode_Target_5XX_Count", "IPv6ProcessedBytes", "IPv6RequestCount", "NewConnectionCount", "ProcessedBytes", "RejectedConnectionCount", "RequestCount", "TargetConnectionErrorCount", "TargetResponseTime", "TargetTLSNegotiationErrorCount", "UnHealthyHostCount"}, + "AWS/ApplicationELB": {"ActiveConnectionCount", "ClientTLSNegotiationErrorCount", "HealthyHostCount", "HTTPCode_ELB_4XX_Count", "HTTPCode_ELB_5XX_Count", "HTTPCode_Target_2XX_Count", "HTTPCode_Target_3XX_Count", "HTTPCode_Target_4XX_Count", "HTTPCode_Target_5XX_Count", "IPv6ProcessedBytes", "IPv6RequestCount", "NewConnectionCount", "ProcessedBytes", "RejectedConnectionCount", "RequestCount", "RequestCountPerTarget", "TargetConnectionErrorCount", "TargetResponseTime", "TargetTLSNegotiationErrorCount", "UnHealthyHostCount"}, "AWS/AutoScaling": {"GroupMinSize", "GroupMaxSize", "GroupDesiredCapacity", "GroupInServiceInstances", "GroupPendingInstances", "GroupStandbyInstances", "GroupTerminatingInstances", "GroupTotalInstances"}, "AWS/Billing": {"EstimatedCharges"}, "AWS/CloudFront": {"Requests", "BytesDownloaded", "BytesUploaded", "TotalErrorRate", "4xxErrorRate", "5xxErrorRate"}, diff --git a/public/app/core/components/PasswordStrength.tsx b/public/app/core/components/PasswordStrength.tsx index 6765d2a51c8..3f690f3d239 100644 --- a/public/app/core/components/PasswordStrength.tsx +++ b/public/app/core/components/PasswordStrength.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import coreModule from '../core_module'; +import { react2AngularDirective } from 'app/core/utils/react2angular'; export interface IProps { password: string; @@ -33,7 +33,5 @@ export class PasswordStrength extends React.Component { } } -coreModule.directive('passwordStrength', function(reactDirective) { - return reactDirective(PasswordStrength, ['password']); -}); +react2AngularDirective('passwordStrength', PasswordStrength, ['password']); diff --git a/public/app/core/components/colorpicker/ColorPalette.tsx b/public/app/core/components/colorpicker/ColorPalette.tsx index 47e0e244e0a..127f2d328ca 100644 --- a/public/app/core/components/colorpicker/ColorPalette.tsx +++ b/public/app/core/components/colorpicker/ColorPalette.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import coreModule from 'app/core/core_module'; import { sortedColors } from 'app/core/utils/colors'; export interface IProps { @@ -23,12 +22,15 @@ export class GfColorPalette extends React.Component { } render() { - const colorPaletteItems = this.paletteColors.map((paletteColor) => { + const colorPaletteItems = this.paletteColors.map(paletteColor => { const cssClass = paletteColor.toLowerCase() === this.props.color.toLowerCase() ? 'fa-circle-o' : 'fa-circle'; return ( -   + +   ); }); @@ -40,6 +42,3 @@ export class GfColorPalette extends React.Component { } } -coreModule.directive('gfColorPalette', function (reactDirective) { - return reactDirective(GfColorPalette, ['color', 'onColorSelect']); -}); diff --git a/public/app/core/components/colorpicker/ColorPicker.tsx b/public/app/core/components/colorpicker/ColorPicker.tsx index baf3f87cf81..dbba75636d0 100644 --- a/public/app/core/components/colorpicker/ColorPicker.tsx +++ b/public/app/core/components/colorpicker/ColorPicker.tsx @@ -2,8 +2,8 @@ import React from 'react'; import ReactDOM from 'react-dom'; import $ from 'jquery'; import Drop from 'tether-drop'; -import coreModule from 'app/core/core_module'; import { ColorPickerPopover } from './ColorPickerPopover'; +import { react2AngularDirective } from 'app/core/utils/react2angular'; export interface IProps { color: string; @@ -27,9 +27,7 @@ export class ColorPicker extends React.Component { } openColorPicker() { - const dropContent = ( - - ); + const dropContent = ; let dropContentElem = document.createElement('div'); ReactDOM.render(dropContent, dropContentElem); @@ -38,12 +36,12 @@ export class ColorPicker extends React.Component { target: this.pickerElem[0], content: dropContentElem, position: 'top center', - classes: 'drop-popover drop-popover--form', - openOn: 'hover', + classes: 'drop-popover', + openOn: 'click', hoverCloseDelay: 200, tetherOptions: { - constraints: [{ to: 'scrollParent', attachment: "none both" }] - } + constraints: [{ to: 'scrollParent', attachment: 'none both' }], + }, }); drop.on('close', this.closeColorPicker); @@ -68,17 +66,14 @@ export class ColorPicker extends React.Component { return (
-
-
+
); } } -coreModule.directive('colorPicker', function (reactDirective) { - return reactDirective(ColorPicker, [ - 'color', - ['onChange', { watchDepth: 'reference', wrapApply: true }] - ]); -}); +react2AngularDirective('colorPicker', ColorPicker, [ + 'color', + ['onChange', { watchDepth: 'reference', wrapApply: true }], +]); diff --git a/public/app/core/components/colorpicker/ColorPickerPopover.tsx b/public/app/core/components/colorpicker/ColorPickerPopover.tsx index 09b6b8ec2c2..270e82ffdd6 100644 --- a/public/app/core/components/colorpicker/ColorPickerPopover.tsx +++ b/public/app/core/components/colorpicker/ColorPickerPopover.tsx @@ -1,7 +1,6 @@ import React from 'react'; import $ from 'jquery'; import tinycolor from 'tinycolor2'; -import coreModule from 'app/core/core_module'; import { GfColorPalette } from './ColorPalette'; import { GfSpectrumPicker } from './SpectrumPicker'; @@ -115,7 +114,3 @@ export class ColorPickerPopover extends React.Component { ); } } - -coreModule.directive('gfColorPickerPopover', function (reactDirective) { - return reactDirective(ColorPickerPopover, ['color', 'onColorSelect']); -}); diff --git a/public/app/core/components/colorpicker/SeriesColorPicker.tsx b/public/app/core/components/colorpicker/SeriesColorPicker.tsx index 2ee2d7571b3..6b6d387a2b2 100644 --- a/public/app/core/components/colorpicker/SeriesColorPicker.tsx +++ b/public/app/core/components/colorpicker/SeriesColorPicker.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import coreModule from 'app/core/core_module'; -import {ColorPickerPopover} from './ColorPickerPopover'; +import { ColorPickerPopover } from './ColorPickerPopover'; +import { react2AngularDirective } from 'app/core/utils/react2angular'; export interface IProps { series: any; @@ -50,6 +50,4 @@ export class SeriesColorPicker extends React.Component { } } -coreModule.directive('seriesColorPicker', function(reactDirective) { - return reactDirective(SeriesColorPicker, ['series', 'onColorChange', 'onToggleAxis']); -}); +react2AngularDirective('seriesColorPicker', SeriesColorPicker, ['series', 'onColorChange', 'onToggleAxis']); diff --git a/public/app/core/components/colorpicker/SpectrumPicker.tsx b/public/app/core/components/colorpicker/SpectrumPicker.tsx index 2ea0fc65ddf..254f06cf145 100644 --- a/public/app/core/components/colorpicker/SpectrumPicker.tsx +++ b/public/app/core/components/colorpicker/SpectrumPicker.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import coreModule from 'app/core/core_module'; import _ from 'lodash'; import $ from 'jquery'; import 'vendor/spectrum'; @@ -71,6 +70,3 @@ export class GfSpectrumPicker extends React.Component { } } -coreModule.directive('gfSpectrumPicker', function (reactDirective) { - return reactDirective(GfSpectrumPicker, ['color', 'options', 'onColorSelect']); -}); diff --git a/public/app/core/components/colorpicker/spectrum_picker.ts b/public/app/core/components/colorpicker/spectrum_picker.ts index c262eaac326..183cebffe2b 100644 --- a/public/app/core/components/colorpicker/spectrum_picker.ts +++ b/public/app/core/components/colorpicker/spectrum_picker.ts @@ -5,6 +5,7 @@ */ import coreModule from '../../core_module'; +/** @ngInject */ export function spectrumPicker() { return { restrict: 'E', diff --git a/public/app/core/utils/file_export.ts b/public/app/core/utils/file_export.ts index a6070cb131c..03fcd147a3e 100644 --- a/public/app/core/utils/file_export.ts +++ b/public/app/core/utils/file_export.ts @@ -1,7 +1,6 @@ import _ from 'lodash'; import moment from 'moment'; - -declare var window: any; +import {saveAs} from 'file-saver'; const DEFAULT_DATETIME_FORMAT = 'YYYY-MM-DDTHH:mm:ssZ'; @@ -69,5 +68,5 @@ export function exportTableDataToCsv(table, excel = false) { export function saveSaveBlob(payload, fname) { var blob = new Blob([payload], { type: "text/csv;charset=utf-8" }); - window.saveAs(blob, fname); + saveAs(blob, fname); } diff --git a/public/app/core/utils/react2angular.ts b/public/app/core/utils/react2angular.ts new file mode 100644 index 00000000000..ad6f7476d6a --- /dev/null +++ b/public/app/core/utils/react2angular.ts @@ -0,0 +1,10 @@ +import coreModule from 'app/core/core_module'; + +export function react2AngularDirective(name: string, component: any, options: any) { + + coreModule.directive(name, ['reactDirective', reactDirective => { + return reactDirective(component, options); + }]); + +} + diff --git a/public/app/features/alerting/alert_def.ts b/public/app/features/alerting/alert_def.ts index c86f0dee775..84481b60ce0 100644 --- a/public/app/features/alerting/alert_def.ts +++ b/public/app/features/alerting/alert_def.ts @@ -51,6 +51,7 @@ var reducerTypes = [ {text: 'median()', value: 'median'}, {text: 'diff()', value: 'diff'}, {text: 'percent_diff()', value: 'percent_diff'}, + {text: 'count_non_null()', value: 'count_non_null'}, ]; var noDataModes = [ diff --git a/public/app/features/alerting/alert_tab_ctrl.ts b/public/app/features/alerting/alert_tab_ctrl.ts index 25c23580ed7..2c273c93a01 100644 --- a/public/app/features/alerting/alert_tab_ctrl.ts +++ b/public/app/features/alerting/alert_tab_ctrl.ts @@ -383,6 +383,7 @@ export class AlertTabCtrl { test() { this.testing = true; + this.testResult = false; var payload = { dashboard: this.dashboardSrv.getCurrent().getSaveModelClone(), diff --git a/public/app/features/alerting/partials/alert_tab.html b/public/app/features/alerting/partials/alert_tab.html index ce737df84d9..9b5a535c727 100644 --- a/public/app/features/alerting/partials/alert_tab.html +++ b/public/app/features/alerting/partials/alert_tab.html @@ -42,7 +42,7 @@ WHEN
- + OF
diff --git a/public/app/features/plugins/plugin_loader.ts b/public/app/features/plugins/plugin_loader.ts index dc766dd7617..5c29ce36495 100644 --- a/public/app/features/plugins/plugin_loader.ts +++ b/public/app/features/plugins/plugin_loader.ts @@ -12,6 +12,9 @@ import {coreModule, appEvents, contextSrv} from 'app/core/core'; import {Observable} from 'rxjs/Observable'; import {Subject} from 'rxjs/Subject'; import * as datemath from 'app/core/utils/datemath'; +import * as fileExport from 'app/core/utils/file_export'; +import * as flatten from 'app/core/utils/flatten'; +import * as ticks from 'app/core/utils/ticks'; import builtInPlugins from './buit_in_plugins'; import d3 from 'vendor/d3/d3'; @@ -54,19 +57,24 @@ exposeToPlugin('rxjs/Observable', Observable); exposeToPlugin('d3', d3); exposeToPlugin('app/plugins/sdk', sdk); + exposeToPlugin('app/core/utils/datemath', datemath); +exposeToPlugin('app/core/utils/file_export', fileExport); +exposeToPlugin('app/core/utils/flatten', flatten); exposeToPlugin('app/core/utils/kbn', kbn); +exposeToPlugin('app/core/utils/ticks', ticks); + exposeToPlugin('app/core/config', config); exposeToPlugin('app/core/time_series', TimeSeries); exposeToPlugin('app/core/time_series2', TimeSeries); exposeToPlugin('app/core/table_model', TableModel); exposeToPlugin('app/core/app_events', appEvents); exposeToPlugin('app/core/core_module', coreModule); -exposeToPlugin('app/core/core_module', coreModule); exposeToPlugin('app/core/core', { coreModule: coreModule, appEvents: appEvents, contextSrv: contextSrv, + __esModule: true }); import 'vendor/flot/jquery.flot'; @@ -79,7 +87,11 @@ import 'vendor/flot/jquery.flot.fillbelow'; import 'vendor/flot/jquery.flot.crosshair'; import 'vendor/flot/jquery.flot.dashes'; -for (let flotDep of ['jquery.flot', 'jquery.flot.pie', 'jquery.flot.time']) { +const flotDeps = [ + 'jquery.flot', 'jquery.flot.pie', 'jquery.flot.time', 'jquery.flot.fillbelow', 'jquery.flot.crosshair', + 'jquery.flot.stack', 'jquery.flot.selection', 'jquery.flot.stackpercent', 'jquery.flot.events' +]; +for (let flotDep of flotDeps) { exposeToPlugin(flotDep, {fakeDep: 1}); } diff --git a/public/app/partials/login.html b/public/app/partials/login.html index fe19d34f8d5..48c94640fc5 100644 --- a/public/app/partials/login.html +++ b/public/app/partials/login.html @@ -20,7 +20,7 @@