diff --git a/.gitignore b/.gitignore index 1ab7068c96a..0e6affc6120 100644 --- a/.gitignore +++ b/.gitignore @@ -9,13 +9,12 @@ awsconfig /public/vendor/npm /tmp vendor/phantomjs/phantomjs +vendor/phantomjs/phantomjs.exe docs/AWS_S3_BUCKET docs/GIT_BRANCH -docs/VERSION docs/GITCOMMIT docs/changed-files -docs/changed-files # locally required config files public/css/*.min.css @@ -40,3 +39,5 @@ profile.cov /pkg/cmd/grafana-cli/grafana-cli /pkg/cmd/grafana-server/grafana-server /examples/*/dist +/packaging/**/*.rpm +/packaging/**/*.deb diff --git a/CHANGELOG.md b/CHANGELOG.md index f2e27dade77..a97b1a77144 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,38 @@ -# 4.4.0 (unreleased) +# 5.0.0 (unreleased) + +## New Features + +* **Table panel**: Render cell values as links that can use url that uses variables from current table row. [#3754](https://github.com/grafana/grafana/issues/3754) + +## Enhancements + +* **GitHub OAuth**: Support for GitHub organizations with 100+ teams. [#8846](https://github.com/grafana/grafana/issues/8846), thx [@skwashd](https://github.com/skwashd) +* **Graphite**: Calls to Graphite api /metrics/find now include panel or dashboad time range (from & until) in most cases, [#8055](https://github.com/grafana/grafana/issues/8055) + +# 4.4.2 (2017-08-01) + +## Bug Fixes + +* **GrafanaDB(mysql)**: Fix for dashboard_version.data column type, now changed to MEDIUMTEXT, fixes [#8813](https://github.com/grafana/grafana/issues/8813) +* **Dashboard(settings)**: Closing setting views using ESC key did not update url correctly, fixes [#8869](https://github.com/grafana/grafana/issues/8869) +* **InfluxDB**: Wrong username/password parameter name when using direct access, fixes [#8789](https://github.com/grafana/grafana/issues/8789) +* **Forms(TextArea)**: Bug fix for no scroll in text areas [#8797](https://github.com/grafana/grafana/issues/8797) +* **Png Render API**: Bug fix for timeout url parameter. It now works as it should. Default value was also increased from 30 to 60 seconds [#8710](https://github.com/grafana/grafana/issues/8710) +* **Search**: Fix for not being able to close search by clicking on right side of search result container, [8848](https://github.com/grafana/grafana/issues/8848) +* **Cloudwatch**: Fix for using variables in templating metrics() query, [8965](https://github.com/grafana/grafana/issues/8965) + +## Changes + +* **Settings(defaults)**: allow_sign_up default changed from true to false [#8743](https://github.com/grafana/grafana/issues/8743) +* **Settings(defaults)**: allow_org_create default changed from true to false + +# 4.4.1 (2017-07-05) + +## Bug Fixes + +* **Migrations**: migration fails where dashboard.created_by is null [#8783](https://github.com/grafana/grafana/issues/8783) + +# 4.4.0 (2017-07-04) ## New Features **Dashboard History**: View dashboard version history, compare any two versions (summary & json diffs), restore to old version. This big feature @@ -10,6 +44,16 @@ Pull Request: [#8472](https://github.com/grafana/grafana/pull/8472) * **Elasticsearch**: Added filter aggregation label [#8420](https://github.com/grafana/grafana/pull/8420), thx [@tianzk](github.com/tianzk) * **Sensu**: Added option for source and handler [#8405](https://github.com/grafana/grafana/pull/8405), thx [@joemiller](github.com/joemiller) * **CSV**: Configurable csv export datetime format [#8058](https://github.com/grafana/grafana/issues/8058), thx [@cederigo](github.com/cederigo) +* **Table Panel**: Column style that preserves formatting/indentation (like pre tag) [#6617](https://github.com/grafana/grafana/issues/6617) +* **DingDing**: Add DingDing Alert Notifier [#8473](https://github.com/grafana/grafana/pull/8473) thx [@jiamliang](https://github.com/jiamliang) + +## Minor Enhancements + +* **Elasticsearch**: Add option for result set size in raw_document [#3426](https://github.com/grafana/grafana/issues/3426) [#8527](https://github.com/grafana/grafana/pull/8527), thx [@mk-dhia](github.com/mk-dhia) + +## Bug Fixes + +* **Graph**: Bug fix for negative values in histogram mode [#8628](https://github.com/grafana/grafana/issues/8628) # 4.3.2 (2017-05-31) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000000..94daa2dc4d5 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@grafana.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/README.md b/README.md index 9d2aabebbf3..41f777d5dec 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Graphite, Elasticsearch, OpenTSDB, Prometheus and InfluxDB. - [What's New in Grafana 4.1](http://docs.grafana.org/guides/whats-new-in-v4-1/) - [What's New in Grafana 4.2](http://docs.grafana.org/guides/whats-new-in-v4-2/) - [What's New in Grafana 4.3](http://docs.grafana.org/guides/whats-new-in-v4-3/) +- [What's New in Grafana 4.4](http://docs.grafana.org/guides/whats-new-in-v4-4/) ## Features diff --git a/appveyor.yml b/appveyor.yml index 303c3abca9e..9f8e9a26622 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -32,6 +32,7 @@ build_script: - grunt release - go run build.go sha-dist - cp dist/* . + - go test -v ./pkg/... artifacts: - path: grafana-*windows-*.* diff --git a/build.go b/build.go index f60a53e5ddb..a1d1d3012ab 100644 --- a/build.go +++ b/build.go @@ -95,7 +95,9 @@ func main() { case "package": grunt(gruntBuildArg("release")...) - createLinuxPackages() + if runtime.GOOS != "windows" { + createLinuxPackages() + } case "pkg-rpm": grunt(gruntBuildArg("release")...) @@ -345,7 +347,11 @@ func ChangeWorkingDir(dir string) { } func grunt(params ...string) { - runPrint("./node_modules/.bin/grunt", params...) + if runtime.GOOS == "windows" { + runPrint(`.\node_modules\.bin\grunt`, params...) + } else { + runPrint("./node_modules/.bin/grunt", params...) + } } func gruntBuildArg(task string) []string { diff --git a/conf/defaults.ini b/conf/defaults.ini index 61c9f7fb5b3..ceb916917ab 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -184,10 +184,10 @@ snapshot_TTL_days = 90 #################################### Users #################################### [users] # disable user signup / registration -allow_sign_up = true +allow_sign_up = false # Allow non admin users to create organizations -allow_org_create = true +allow_org_create = false # Set to true to automatically assign new users to the default organization (id 1) auto_assign_org = true @@ -204,6 +204,11 @@ login_hint = email or username # Default UI theme ("dark" or "light") default_theme = dark +# External user management +external_manage_link_url = +external_manage_link_name = +external_manage_info = + [auth] # Set to true to disable (hide) the login form, useful if you use OAuth disable_login_form = false diff --git a/conf/sample.ini b/conf/sample.ini index 65ada5b9468..e33f408ecad 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -191,6 +191,11 @@ # Default UI theme ("dark" or "light") ;default_theme = dark +# External user management, these options affect the organization users view +;external_manage_link_url = +;external_manage_link_name = +;external_manage_info = + [auth] # Set to true to disable (hide) the login form, useful if you use OAuth, defaults to false ;disable_login_form = false @@ -298,7 +303,7 @@ # Use space to separate multiple modes, e.g. "console file" ;mode = console file -# Either "trace", "debug", "info", "warn", "error", "critical", default is "info" +# Either "debug", "info", "warn", "error", "critical", default is "info" ;level = info # optional settings to set different levels for specific loggers. Ex filters = sqlstore:debug diff --git a/docker/blocks/graphite/fig b/docker/blocks/graphite/fig index 60acb8c1131..b7e030e388e 100644 --- a/docker/blocks/graphite/fig +++ b/docker/blocks/graphite/fig @@ -4,7 +4,6 @@ graphite: - "8080:80" - "2003:2003" volumes: - - /var/docker/gfdev/graphite:/opt/graphite/storage/whisper - /etc/localtime:/etc/localtime:ro - /etc/timezone:/etc/timezone:ro diff --git a/docs/sources/alerting/notifications.md b/docs/sources/alerting/notifications.md index cd1316eb479..359315650d9 100644 --- a/docs/sources/alerting/notifications.md +++ b/docs/sources/alerting/notifications.md @@ -22,7 +22,7 @@ to add and configure a `notification` channel (can be email, Pagerduty or other {{< imgbox max-width="40%" img="/img/docs/v43/alert_notifications_menu.png" caption="Alerting Notification Channels" >}} -On the Notification Channels page hit the `New Channel` button to go the the page where you +On the Notification Channels page hit the `New Channel` button to go the page where you can configure and setup a new Notification Channel. You specify name and type, and type specific options. You can also test the notification to make @@ -92,6 +92,26 @@ Example json body: - **state** - The possible values for alert state are: `ok`, `paused`, `alerting`, `pending`, `no_data`. +### DingDing/DingTalk + +[Instructions in Chinese](https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.p2lr6t&treeId=257&articleId=105733&docType=1). + +In DingTalk PC Client: + +1. Click "more" icon on left bottom of the panel. + +2. Click "Robot Manage" item in the pop menu, there will be a new panel call "Robot Manage". + +3. In the "Robot Manage" panel, select "customised: customised robot with Webhook". + +4. In the next new panel named "robot detail", click "Add" button. + +5. In "Add Robot" panel, input a nickname for the robot and select a "message group" which the robot will join in. click "next". + +6. There will be a Webhook URL in the panel, looks like this: https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxx. Copy this URL to the grafana Dingtalk setting page and then click "finish". + +Dingtalk supports the following "message type": `text`, `link` and `markdown`. Only the `text` message type is supported. + ### Other Supported Notification Channels Grafana also supports the following Notification Channels: @@ -114,7 +134,7 @@ Grafana also supports the following Notification Channels: # Enable images in notifications {#external-image-store} -Grafana can render the panel associated with the alert rule and include that in the notification. Most Notification Channels require that this image be publicly accessable (Slack and PagerDuty for example). In order to include images in alert notifications, Grafana can upload the image to an image store. It currently supports +Grafana can render the panel associated with the alert rule and include that in the notification. Most Notification Channels require that this image be publicly accessible (Slack and PagerDuty for example). In order to include images in alert notifications, Grafana can upload the image to an image store. It currently supports Amazon S3 and Webdav for this. So to set that up you need to configure the [external image uploader](/installation/configuration/#external-image-storage) in your grafana-server ini config file. Currently only the Email Channels attaches images if no external image store is specified. To include images in alert notifications for other channels then you need to set up an external image store. diff --git a/docs/sources/archive.md b/docs/sources/archive.md index 7102395f4c7..8ed38b933e6 100644 --- a/docs/sources/archive.md +++ b/docs/sources/archive.md @@ -14,5 +14,7 @@ of Grafana. - [Latest](http://docs.grafana.org) - [Version 4.2](http://docs.grafana.org/v4.2) +- [Version 4.1](http://docs.grafana.org/v4.1) +- [Version 4.0](http://docs.grafana.org/v4.0) - [Version 3.1](http://docs.grafana.org/v3.1) - [Version 3.0](http://docs.grafana.org/v3.0) diff --git a/docs/sources/features/datasources/cloudwatch.md b/docs/sources/features/datasources/cloudwatch.md index 8d77e5c59c0..61dd90d2881 100644 --- a/docs/sources/features/datasources/cloudwatch.md +++ b/docs/sources/features/datasources/cloudwatch.md @@ -84,8 +84,8 @@ Name | Description *metrics(namespace, [region])* | Returns a list of metrics in the namespace. (specify region for custom metrics) *dimension_keys(namespace)* | Returns a list of dimension keys in the namespace. *dimension_values(region, namespace, metric, dimension_key)* | Returns a list of dimension values matching the specified `region`, `namespace`, `metric` and `dimension_key`. -*ebs_volume_ids(region, instance_id)* | Returns a list of volume id matching the specified `region`, `instance_id`. -*ec2_instance_attribute(region, attribute_name, filters)* | Returns a list of attribute matching the specified `region`, `attribute_name`, `filters`. +*ebs_volume_ids(region, instance_id)* | Returns a list of volume ids matching the specified `region`, `instance_id`. +*ec2_instance_attribute(region, attribute_name, filters)* | Returns a list of attributes matching the specified `region`, `attribute_name`, `filters`. For details about the metrics CloudWatch provides, please refer to the [CloudWatch documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/CW_Support_For_AWS.html). @@ -101,10 +101,13 @@ Query | Service *dimension_values(us-east-1,AWS/RDS,CPUUtilization,DBInstanceIdentifier)* | RDS *dimension_values(us-east-1,AWS/S3,BucketSizeBytes,BucketName)* | S3 -#### ec2_instance_attribute JSON filters +## ec2_instance_attribute examples -The `ec2_instance_attribute` query take `filters` in JSON format. +### JSON filters + +The `ec2_instance_attribute` query takes `filters` in JSON format. You can specify [pre-defined filters of ec2:DescribeInstances](http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html). +Note that the actual filtering takes place on Amazon's servers, not in Grafana. Filters syntax: @@ -116,6 +119,45 @@ Example `ec2_instance_attribute()` query ec2_instance_attribute(us-east-1, InstanceId, { "tag:Environment": [ "production" ] }) +### Selecting Attributes + +Only 1 attribute per instance can be returned. Any flat attribute can be selected (i.e. if the attribute has a single value and isn't an object or array). Below is a list of available flat attributes: + + * `AmiLaunchIndex` + * `Architecture` + * `ClientToken` + * `EbsOptimized` + * `EnaSupport` + * `Hypervisor` + * `IamInstanceProfile` + * `ImageId` + * `InstanceId` + * `InstanceLifecycle` + * `InstanceType` + * `KernelId` + * `KeyName` + * `LaunchTime` + * `Platform` + * `PrivateDnsName` + * `PrivateIpAddress` + * `PublicDnsName` + * `PublicIpAddress` + * `RamdiskId` + * `RootDeviceName` + * `RootDeviceType` + * `SourceDestCheck` + * `SpotInstanceRequestId` + * `SriovNetSupport` + * `SubnetId` + * `VirtualizationType` + * `VpcId` + +Tags can be selected by prepending the tag name with `Tags.` + +Example `ec2_instance_attribute()` query + + ec2_instance_attribute(us-east-1, Tags.Name, { "tag:Team": [ "sysops" ] }) + ## Cost Amazon provides 1 million CloudWatch API requests each month at no additional charge. Past this, diff --git a/docs/sources/features/datasources/influxdb.md b/docs/sources/features/datasources/influxdb.md index 2837363e145..3f86b9ee703 100644 --- a/docs/sources/features/datasources/influxdb.md +++ b/docs/sources/features/datasources/influxdb.md @@ -36,7 +36,7 @@ Name | Description ### Proxy vs Direct access Proxy access means that the Grafana backend will proxy all requests from the browser. So requests to InfluxDB will be channeled through -`grafana-server`. This means that the URL you specify needs to be accessable from the server you are running Grafana on. Proxy access +`grafana-server`. This means that the URL you specify needs to be accessible from the server you are running Grafana on. Proxy access mode is also more secure as the username & password will never reach the browser. ## Query Editor @@ -88,8 +88,8 @@ You can switch to raw query mode by clicking hamburger icon and then `Switch edi - $m = replaced with measurement name - $measurement = replaced with measurement name - $col = replaced with column name -- $tag_hostname = replaced with the value of the hostname tag -- You can also use [[tag_hostname]] pattern replacement syntax +- $tag_exampletag = replaced with the value of the `exampletag` tag. To use your tag as an alias in the ALIAS BY field then the tag must be used to group by in the query. +- You can also use [[tag_hostname]] pattern replacement syntax. For example, in the ALIAS BY field using this text `Host: [[tag_hostname]]` would substitute in the `hostname` tag value for each legend value and an example legend value would be: `Host: server1`. ### Table query / raw data @@ -132,7 +132,7 @@ You can fetch key names for a given measurement. SHOW TAG KEYS [FROM ] ``` -If you have a variable with key names you can use this variable in a group by clause. This will allow you to change group by using the variable dropdown a the top +If you have a variable with key names you can use this variable in a group by clause. This will allow you to change group by using the variable dropdown at the top of the dashboard. ### Using variables in queries diff --git a/docs/sources/features/datasources/prometheus.md b/docs/sources/features/datasources/prometheus.md index 4b5c3f115e8..a6f544914b8 100644 --- a/docs/sources/features/datasources/prometheus.md +++ b/docs/sources/features/datasources/prometheus.md @@ -46,7 +46,8 @@ Name | Description *Query expression* | Prometheus query expression, check out the [Prometheus documentation](http://prometheus.io/docs/querying/basics/). *Legend format* | Controls the name of the time series, using name or pattern. For example `{{hostname}}` will be replaced with label value for the label `hostname`. *Min step* | Set a lower limit for the Prometheus step option. Step controls how big the jumps are when the Prometheus query engine performs range queries. Sadly there is no official prometheus documentation to link to for this very important option. -*Resolution* | Controls the step option. Small steps create high-resolution graphs but can be slow over larger time ranges, lowering the resolution can speed things up. `1/2` will try to set step option to generate 1 data point for every other pixel. A value of `1/10` will try to set step option so there is a data point every 10 pixels.*Metric lookup* | Search for metric names in this input field. +*Resolution* | Controls the step option. Small steps create high-resolution graphs but can be slow over larger time ranges, lowering the resolution can speed things up. `1/2` will try to set step option to generate 1 data point for every other pixel. A value of `1/10` will try to set step option so there is a data point every 10 pixels. +*Metric lookup* | Search for metric names in this input field. *Format as* | **(New in v4.3)** Switch between Table & Time series. Table format will only work in the Table panel. ## Templating @@ -77,7 +78,7 @@ For details of *metric names*, *label names* and *label values* are please refer There are two syntaxes: - `$` Example: rate(http_requests_total{job=~"$job"}[5m]) -- `[[varname]]` Example: rate(http_requests_total{job="my[[job]]"}[5m]) +- `[[varname]]` Example: rate(http_requests_total{job=~"[[job]]"}[5m]) Why two ways? The first syntax is easier to read and write but does not allow you to use a variable in the middle of a word. When the *Multi-value* or *Include all value* options are enabled, Grafana converts the labels from plain text to a regex compatible string. Which means you have to use `=~` instead of `=`. diff --git a/docs/sources/features/datasources/testdata.md b/docs/sources/features/datasources/testdata.md index 3475980d2b4..491e5b60fbb 100644 --- a/docs/sources/features/datasources/testdata.md +++ b/docs/sources/features/datasources/testdata.md @@ -13,7 +13,7 @@ weight = 20 The purpose of this data sources is to make it easier to create fake data for any panel. Using `Grafana TestData` you can build your own time series and have any panel render it. -This make is much easier to verify functionally since the data can be shared very +This make is much easier to verify functionally since the data can be shared very easily. ## Enable diff --git a/docs/sources/features/panels/graph.md b/docs/sources/features/panels/graph.md index fdd3f271184..5dbf307b484 100644 --- a/docs/sources/features/panels/graph.md +++ b/docs/sources/features/panels/graph.md @@ -67,6 +67,20 @@ The ``Left Y`` and ``Right Y`` can be customized using: Axes can also be hidden by unchecking the appropriate box from `Show Axis`. +### X-Axis Mode + +There are three options: + +- The default option is `Time` and means the x-axis represents time and that the data is grouped by time (for example, by hour or by minute). + +- The `Series` option means that the data is grouped by series and not by time. The y-axis still represents the value. + + + +- The `Histogram` option converts the graph into a histogram. A Histogram is a kind of bar chart that groups numbers into ranges, often called buckets or bins. Taller bars show that more data falls in that range. Histograms and buckets are described in more detail [here](http://docs.grafana.org/features/panels/heatmap/#histograms-and-buckets). + + + ### Legend The legend hand be hidden by checking the ``Show`` checkbox. If it's shown, it can be diff --git a/docs/sources/features/panels/singlestat.md b/docs/sources/features/panels/singlestat.md index eb9b2f26ea5..7447f6c9b84 100644 --- a/docs/sources/features/panels/singlestat.md +++ b/docs/sources/features/panels/singlestat.md @@ -34,7 +34,7 @@ The singlestat panel has a normal query editor to allow you define your exact me * `delta` - The total incremental increase (of a counter) in the series. An attempt is made to account for counter resets, but this will only be accurate for single instance metrics. Used to show total counter increase in time series. * `diff` - The difference betwen 'current' (last value) and 'first'. * `range` - The difference between 'min' and 'max'. Useful the show the range of change for a gauge. -4. `Postfixes`: The Postfix fields let you define a custom label and font-size (as a %) to appear *after* the value +4. `Prefix/Postfix`: The Prefix/Postfix fields let you define a custom label and font-size (as a %) to appear *before/after* the value. The `$__name` variable can be used here to use the series name or alias from the metric query. 5. `Units`: Units are appended to the the Singlestat within the panel, and will respect the color and threshold settings for the value. 6. `Decimals`: The Decimal field allows you to override the automatic decimal precision, and set it explicitly. diff --git a/docs/sources/guides/whats-new-in-v4-4.md b/docs/sources/guides/whats-new-in-v4-4.md new file mode 100644 index 00000000000..e99f3ea3719 --- /dev/null +++ b/docs/sources/guides/whats-new-in-v4-4.md @@ -0,0 +1,50 @@ ++++ +title = "What's New in Grafana v4.4" +description = "Feature & improvement highlights for Grafana v4.4" +keywords = ["grafana", "new", "documentation", "4.4.0"] +type = "docs" +[menu.docs] +name = "Version 4.4" +identifier = "v4.4" +parent = "whatsnew" +weight = -3 ++++ + +## What's New in Grafana v4.4 + +Grafana v4.4 is now [available for download](https://grafana.com/grafana/download/4.4.0). + +**Highlights**: + +- Dashboard History - version control for dashboards. + +## New Features + +**Dashboard History**: View dashboard version history, compare any two versions (summary & json diffs), restore to old version. This big feature +was contributed by **Walmart Labs**. Big thanks to them for this massive contribution! +Initial feature request: [#4638](https://github.com/grafana/grafana/issues/4638) +Pull Request: [#8472](https://github.com/grafana/grafana/pull/8472) + +## Enhancements +* **Elasticsearch**: Added filter aggregation label [#8420](https://github.com/grafana/grafana/pull/8420), thx [@tianzk](github.com/tianzk) +* **Sensu**: Added option for source and handler [#8405](https://github.com/grafana/grafana/pull/8405), thx [@joemiller](github.com/joemiller) +* **CSV**: Configurable csv export datetime format [#8058](https://github.com/grafana/grafana/issues/8058), thx [@cederigo](github.com/cederigo) +* **Table Panel**: Column style that preserves formatting/indentation (like pre tag) [#6617](https://github.com/grafana/grafana/issues/6617) +* **DingDing**: Add DingDing Alert Notifier [#8473](https://github.com/grafana/grafana/pull/8473) thx [@jiamliang](https://github.com/jiamliang) + +## Minor Enhancements + +* **Elasticsearch**: Add option for result set size in raw_document [#3426](https://github.com/grafana/grafana/issues/3426) [#8527](https://github.com/grafana/grafana/pull/8527), thx [@mk-dhia](github.com/mk-dhia) + +## Bug Fixes + +* **Graph**: Bug fix for negative values in histogram mode [#8628](https://github.com/grafana/grafana/issues/8628) + +## Download + +Head to the [v4.4 download page](https://grafana.com/grafana/download) for download links & instructions. + +## Thanks + +A big thanks to all the Grafana users who contribute by submitting PRs, bug reports, helping out on our [community site](https://community.grafana.com/) and providing feedback! + diff --git a/docs/sources/http_api/dashboard.md b/docs/sources/http_api/dashboard.md index a7b7ae87a62..899c76ce6e0 100644 --- a/docs/sources/http_api/dashboard.md +++ b/docs/sources/http_api/dashboard.md @@ -46,6 +46,7 @@ JSON Body schema: - **dashboard** – The complete dashboard model, id = null to create a new dashboard - **overwrite** – Set to true if you want to overwrite existing dashboard with newer version or with same dashboard title. +- **message** - Set a commit message for the version history. **Example Response**: @@ -239,7 +240,7 @@ Get all tags of dashboards `GET /api/search/` -Status Codes: +Query parameters: - **query** – Search Query - **tag** – Tag to use @@ -268,9 +269,3 @@ Status Codes: "isStarred":false } ] - - "email":"admin@mygraf.com", - "login":"admin", - "role":"Admin" - } - ] diff --git a/docs/sources/http_api/dashboard_versions.md b/docs/sources/http_api/dashboard_versions.md new file mode 100644 index 00000000000..3d0ec27a3a3 --- /dev/null +++ b/docs/sources/http_api/dashboard_versions.md @@ -0,0 +1,321 @@ ++++ +title = "Dashboard Versions HTTP API " +description = "Grafana Dashboard Versions HTTP API" +keywords = ["grafana", "http", "documentation", "api", "dashboard", "versions"] +aliases = ["/http_api/dashboardversions/"] +type = "docs" +[menu.docs] +name = "Dashboard Versions" +parent = "http_api" ++++ + +# Dashboard Versions + +## Get all dashboard versions + +Query parameters: + +- **limit** - Maximum number of results to return +- **start** - Version to start from when returning queries + +`GET /api/dashboards/id/:dashboardId/versions` + +Gets all existing dashboard versions for the dashboard with the given `dashboardId`. + +**Example request for getting all dashboard versions**: + +```http +GET /api/dashboards/id/1/versions?limit=2?start=0 HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk +``` + +**Example Response** + +```http +HTTP/1.1 200 OK +Content-Type: application/json; charset=UTF-8 +Content-Length: 428 + +[ + { + "id": 2, + "dashboardId": 1, + "parentVersion": 1, + "restoredFrom": 0, + "version": 2, + "created": "2017-06-08T17:24:33-04:00", + "createdBy": "admin", + "message": "Updated panel title" + }, + { + "id": 1, + "dashboardId": 1, + "parentVersion": 0, + "restoredFrom": 0, + "version": 1, + "created": "2017-06-08T17:23:33-04:00", + "createdBy": "admin", + "message": "Initial save" + } +] +``` + +Status Codes: + +- **200** - Ok +- **400** - Errors +- **401** - Unauthorized +- **404** - Dashboard version not found + +## Get dashboard version + +`GET /api/dashboards/id/:dashboardId/versions/:id` + +Get the dashboard version with the given id, for the dashboard with the given id. + +**Example request for getting a dashboard version**: + +```http +GET /api/dashboards/id/1/versions/1 HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk +``` + +**Example response**: + +```http +HTTP/1.1 200 OK +Content-Type: application/json; charset=UTF-8 +Content-Length: 1300 + +{ + "id": 1, + "dashboardId": 1, + "parentVersion": 0, + "restoredFrom": 0, + "version": 1, + "created": "2017-04-26T17:18:38-04:00", + "message": "Initial save", + "data": { + "annotations": { + "list": [ + + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "hideControls": false, + "id": 1, + "links": [ + + ], + "rows": [ + { + "collapse": false, + "height": "250px", + "panels": [ + + ], + "repeat": null, + "repeatIteration": null, + "repeatRowId": null, + "showTitle": false, + "title": "Dashboard Row", + "titleSize": "h6" + } + ], + "schemaVersion": 14, + "style": "dark", + "tags": [ + + ], + "templating": { + "list": [ + + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "browser", + "title": "test", + "version": 1 + }, + "createdBy": "admin" +} +``` + +Status Codes: + +- **200** - Ok +- **401** - Unauthorized +- **404** - Dashboard version not found + +## Restore dashboard + +`POST /api/dashboards/id/:dashboardId/restore` + +Restores a dashboard to a given dashboard version. + +**Example request for restoring a dashboard version**: + +```http +POST /api/dashboards/id/1/restore +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +{ + "version": 1 +} +``` + +JSON body schema: + +- **version** - The dashboard version to restore to + +**Example response**: + +```http +HTTP/1.1 200 OK +Content-Type: application/json; charset=UTF-8 +Content-Length: 67 + +{ + "slug": "my-dashboard", + "status": "success", + "version": 3 +} +``` + +JSON response body schema: + +- **slug** - the URL friendly slug of the dashboard's title +- **status** - whether the restoration was successful or not +- **version** - the new dashboard version, following the restoration + +Status codes: + +- **200** - OK +- **401** - Unauthorized +- **404** - Not found (dashboard not found or dashboard version not found) +- **500** - Internal server error (indicates issue retrieving dashboard tags from database) + +**Example error response** + +```http +HTTP/1.1 404 Not Found +Content-Type: application/json; charset=UTF-8 +Content-Length: 46 + +{ + "message": "Dashboard version not found" +} +``` + +JSON response body schema: + +- **message** - Message explaining the reason for the request failure. + +## Compare dashboard versions + +`POST /api/dashboards/calculate-diff` + +Compares two dashboard versions by calculating the JSON diff of them. + +**Example request**: + +```http +POST /api/dashboards/calculate-diff HTTP/1.1 +Accept: text/html +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +{ + "base": { + "dashboardId": 1, + "version": 1 + }, + "new": { + "dashboardId": 1, + "version": 2 + }, + "diffType": "json" +} +``` + +JSON body schema: + +- **base** - an object representing the base dashboard version +- **new** - an object representing the new dashboard version +- **diffType** - the type of diff to return. Can be "json" or "basic". + +**Example response (JSON diff)**: + +```http +HTTP/1.1 200 OK +Content-Type: text/html; charset=UTF-8 + +

+ +

+``` + +The response is a textual respresentation of the diff, with the dashboard values being in JSON, similar to the diffs seen on sites like GitHub or GitLab. + +Status Codes: + +- **200** - Ok +- **400** - Bad request (invalid JSON sent) +- **401** - Unauthorized +- **404** - Not found + +**Example response (basic diff)**: + +```http +HTTP/1.1 200 OK +Content-Type: text/html; charset=UTF-8 + +
+ +
+``` + +The response here is a summary of the changes, derived from the diff between the two JSON objects. + +Status Codes: + +- **200** - OK +- **400** - Bad request (invalid JSON sent) +- **401** - Unauthorized +- **404** - Not found diff --git a/docs/sources/http_api/snapshot.md b/docs/sources/http_api/snapshot.md index f0b8304cd24..d466d01e051 100644 --- a/docs/sources/http_api/snapshot.md +++ b/docs/sources/http_api/snapshot.md @@ -52,6 +52,15 @@ parent = "http_api" "expires": 3600 } +JSON Body schema: + +- **dashboard** – Required. The complete dashboard model. +- **name** – Optional. snapshot name +- **expires** - Optional. When the snapshot should expire in seconds. 3600 is 1 hour, 86400 is 1 day. Default is never to expire. +- **external** - Optional. Save the snapshot on an external server rather than locally. Default is `false`. +- **key** - Optional. Define the unique key. Required if **external** is `true`. +- **deleteKey** - Optional. Unique key used to delete the snapshot. It is different from the **key** so that only the creator can delete the snapshot. Required if **external** is `true`. + **Example Response**: HTTP/1.1 200 diff --git a/docs/sources/installation/configuration.md b/docs/sources/installation/configuration.md index 20f191b26e1..2eaa3018639 100644 --- a/docs/sources/installation/configuration.md +++ b/docs/sources/installation/configuration.md @@ -203,6 +203,12 @@ For MySQL, use either `true`, `false`, or `skip-verify`. (MySQL only) The common name field of the certificate used by the `mysql` server. Not necessary if `ssl_mode` is set to `skip-verify`. +### max_idle_conn +The maximum number of connections in the idle connection pool. + +### max_open_conn +The maximum number of open connections to the database. +
## [security] @@ -240,13 +246,13 @@ Define a white list of allowed ips/domains to use in data sources. Format: `ip_o ### allow_sign_up Set to `false` to prohibit users from being able to sign up / create -user accounts. Defaults to `true`. The admin user can still create +user accounts. Defaults to `false`. The admin user can still create users from the [Grafana Admin Pages](../../reference/admin) ### allow_org_create Set to `false` to prohibit users from creating new organizations. -Defaults to `true`. +Defaults to `false`. ### auto_assign_org diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index b83ebdc4a12..388fe3834f0 100644 --- a/docs/sources/installation/debian.md +++ b/docs/sources/installation/debian.md @@ -15,7 +15,7 @@ weight = 1 Description | Download ------------ | ------------- -Stable for Debian-based Linux | [grafana_4.3.1_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.3.1_amd64.deb) +Stable for Debian-based Linux | [grafana_4.4.2_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.4.2_amd64.deb) Read [Upgrading Grafana]({{< relref "installation/upgrading.md" >}}) for tips and guidance on updating an existing installation. @@ -23,9 +23,9 @@ installation. ## Install Stable ```bash -wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.3.1_amd64.deb +wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.4.2_amd64.deb sudo apt-get install -y adduser libfontconfig -sudo dpkg -i grafana_4.3.1_amd64.deb +sudo dpkg -i grafana_4.4.2_amd64.deb ``` + +
diff --git a/public/app/core/components/search/search.ts b/public/app/core/components/search/search.ts index f6810f2bc76..4644e7a5893 100644 --- a/public/app/core/components/search/search.ts +++ b/public/app/core/components/search/search.ts @@ -77,7 +77,7 @@ export class SearchCtrl { this.moveSelection(-1); } if (evt.keyCode === 13) { - if (this.$scope.tagMode) { + if (this.tagsMode) { var tag = this.results[this.selectedIndex]; if (tag) { this.filterByTag(tag.term, null); diff --git a/public/app/core/core.ts b/public/app/core/core.ts index 6037ff430d5..8dd60942c7b 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -34,6 +34,7 @@ import {switchDirective} from './components/switch'; import {dashboardSelector} from './components/dashboard_selector'; import {queryPartEditorDirective} from './components/query_part/query_part_editor'; import {WizardFlow} from './components/wizard/wizard'; +import {formDropdownDirective} from './components/form_dropdown/form_dropdown'; import 'app/core/controllers/all'; import 'app/core/services/all'; import 'app/core/routes/routes'; @@ -45,6 +46,8 @@ import {assignModelProperties} from './utils/model_utils'; import {contextSrv} from './services/context_srv'; import {KeybindingSrv} from './services/keybindingSrv'; import {helpModal} from './components/help/help'; +import {collapseBox} from './components/collapse_box'; +import {JsonExplorer} from './components/json_explorer/json_explorer'; import {NavModelSrv, NavModel} from './nav_model_srv'; export { @@ -65,10 +68,13 @@ export { queryPartEditorDirective, WizardFlow, colors, + formDropdownDirective, assignModelProperties, contextSrv, KeybindingSrv, helpModal, + collapseBox, + JsonExplorer, NavModelSrv, NavModel, }; diff --git a/public/app/core/directives/dash_edit_link.js b/public/app/core/directives/dash_edit_link.js index 3e4bdd4c5c7..d9a439b95c2 100644 --- a/public/app/core/directives/dash_edit_link.js +++ b/public/app/core/directives/dash_edit_link.js @@ -25,7 +25,6 @@ function ($, angular, coreModule) { function hideEditorPane(hideToShowOtherView) { if (editorScope) { editorScope.dismiss(hideToShowOtherView); - scope.appEvent('dash-editor-hidden'); } } @@ -35,7 +34,7 @@ function ($, angular, coreModule) { options.html = editViewMap[options.editview].html; } - if (lastEditView === options.editview) { + if (lastEditView && lastEditView === options.editview) { hideEditorPane(false); return; } @@ -61,7 +60,15 @@ function ($, angular, coreModule) { var urlParams = $location.search(); if (options.editview === urlParams.editview) { delete urlParams.editview; - $location.search(urlParams); + + // even though we always are in apply phase here + // some angular bug is causing location search updates to + // not happen always so this is a hack fix or that + setTimeout(function() { + $rootScope.$apply(function() { + $location.search(urlParams); + }); + }); } } }; diff --git a/public/app/core/directives/misc.js b/public/app/core/directives/misc.js index 8f6f1fc52a7..f3667c42c13 100644 --- a/public/app/core/directives/misc.js +++ b/public/app/core/directives/misc.js @@ -1,9 +1,10 @@ define([ 'angular', + 'require', '../core_module', 'app/core/utils/kbn', ], -function (angular, coreModule, kbn) { +function (angular, require, coreModule, kbn) { 'use strict'; coreModule.default.directive('tip', function($compile) { @@ -18,6 +19,29 @@ function (angular, coreModule, kbn) { }; }); + coreModule.default.directive('clipboardButton', function() { + return { + scope: { + getText: '&clipboardButton' + }, + link: function(scope, elem) { + require(['vendor/clipboard/dist/clipboard'], function(Clipboard) { + scope.clipboard = new Clipboard(elem[0], { + text: function() { + return scope.getText(); + } + }); + }); + + scope.$on('$destroy', function() { + if (scope.clipboard) { + scope.clipboard.destroy(); + } + }); + } + }; + }); + coreModule.default.directive('compile', function($compile) { return { restrict: 'A', @@ -77,10 +101,10 @@ function (angular, coreModule, kbn) { text + tip + ''; var template = - '' + - ' '; + '' + + ' '; template = template + label; elem.addClass('gf-form-checkbox'); @@ -105,7 +129,7 @@ function (angular, coreModule, kbn) { var li = '' + '' + (item.text || '') + ''; + '>' + (item.text || '') + ''; if (item.submenu && item.submenu.length) { li += buildTemplate(item.submenu).join('\n'); diff --git a/public/app/core/directives/plugin_component.ts b/public/app/core/directives/plugin_component.ts index 4c098f60a4c..d0f21501e7e 100644 --- a/public/app/core/directives/plugin_component.ts +++ b/public/app/core/directives/plugin_component.ts @@ -109,7 +109,7 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $ baseUrl: ds.meta.baseUrl, name: 'query-ctrl-' + ds.meta.id, bindings: {target: "=", panelCtrl: "=", datasource: "="}, - attrs: {"target": "target", "panel-ctrl": "ctrl", datasource: "datasource"}, + attrs: {"target": "target", "panel-ctrl": "ctrl.panelCtrl", datasource: "datasource"}, Component: dsModule.QueryCtrl }; }); @@ -127,7 +127,7 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $ baseUrl: ds.meta.baseUrl, name: 'query-options-ctrl-' + ds.meta.id, bindings: {panelCtrl: "="}, - attrs: {"panel-ctrl": "ctrl"}, + attrs: {"panel-ctrl": "ctrl.panelCtrl"}, Component: dsModule.QueryOptionsCtrl }; }); @@ -181,7 +181,7 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $ return System.import(appModel.module).then(function(appModule) { return { baseUrl: appModel.baseUrl, - name: 'app-page-' + appModel.appId + '-' + scope.ctrl.page.slug, + name: 'app-page-' + appModel.id + '-' + scope.ctrl.page.slug, bindings: {appModel: "="}, attrs: {"app-model": "ctrl.appModel"}, Component: appModule[scope.ctrl.page.component], diff --git a/public/app/core/services/backend_srv.ts b/public/app/core/services/backend_srv.ts index cc1a8644a51..bffdfa05914 100644 --- a/public/app/core/services/backend_srv.ts +++ b/public/app/core/services/backend_srv.ts @@ -4,6 +4,7 @@ import angular from 'angular'; import _ from 'lodash'; import config from 'app/core/config'; import coreModule from 'app/core/core_module'; +import appEvents from 'app/core/app_events'; export class BackendSrv { inFlightRequests = {}; @@ -150,7 +151,10 @@ export class BackendSrv { } } - return this.$http(options).catch(err => { + return this.$http(options).then(response => { + appEvents.emit('ds-request-response', response); + return response; + }).catch(err => { if (err.status === this.HTTP_REQUEST_CANCELLED) { throw {err, cancelled: true}; } @@ -166,7 +170,7 @@ export class BackendSrv { }); } - //populate error obj on Internal Error + // populate error obj on Internal Error if (_.isString(err.data) && err.status === 500) { err.data = { error: err.statusText, @@ -175,11 +179,13 @@ export class BackendSrv { } // for Prometheus - if (!err.data.message && _.isString(err.data.error)) { + if (err.data && !err.data.message && _.isString(err.data.error)) { err.data.message = err.data.error; } + appEvents.emit('ds-request-error', err); throw err; + }).finally(() => { // clean up if (options.requestId) { diff --git a/public/app/core/utils/kbn.js b/public/app/core/utils/kbn.js index d619d9c4977..6f8340d5940 100644 --- a/public/app/core/utils/kbn.js +++ b/public/app/core/utils/kbn.js @@ -841,7 +841,7 @@ function($, _) { { text: 'temperature', submenu: [ - {text: 'Celcius (°C)', value: 'celsius' }, + {text: 'Celsius (°C)', value: 'celsius' }, {text: 'Farenheit (°F)', value: 'farenheit' }, {text: 'Kelvin (K)', value: 'kelvin' }, ] diff --git a/public/app/core/utils/ticks.ts b/public/app/core/utils/ticks.ts index 7e7abbcd8f0..b033e9247a1 100644 --- a/public/app/core/utils/ticks.ts +++ b/public/app/core/utils/ticks.ts @@ -25,3 +25,43 @@ export function tickStep(start: number, stop: number, count: number): number { return stop < start ? -step1 : step1; } + +export function getScaledDecimals(decimals, tick_size) { + return decimals - Math.floor(Math.log(tick_size) / Math.LN10); +} + +/** + * Calculate tick size based on min and max values, number of ticks and precision. + * @param min Axis minimum + * @param max Axis maximum + * @param noTicks Number of ticks + * @param tickDecimals Tick decimal precision + */ +export function getFlotTickSize(min: number, max: number, noTicks: number, tickDecimals: number) { + var delta = (max - min) / noTicks, + dec = -Math.floor(Math.log(delta) / Math.LN10), + maxDec = tickDecimals; + + var magn = Math.pow(10, -dec), + norm = delta / magn, // norm is between 1.0 and 10.0 + size; + + if (norm < 1.5) { + size = 1; + } else if (norm < 3) { + size = 2; + // special case for 2.5, requires an extra decimal + if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { + size = 2.5; + ++dec; + } + } else if (norm < 7.5) { + size = 5; + } else { + size = 10; + } + + size *= magn; + + return size; +} diff --git a/public/app/features/admin/partials/settings.html b/public/app/features/admin/partials/settings.html index 150d3145f45..c8aa4fd3790 100644 --- a/public/app/features/admin/partials/settings.html +++ b/public/app/features/admin/partials/settings.html @@ -6,7 +6,7 @@
- These system settings are defined in grafana.ini or custom.ini (or overriden in ENV variables). + These system settings are defined in grafana.ini or custom.ini (or overridden in ENV variables). To change these you currently need to restart grafana.
diff --git a/public/app/features/alerting/alert_tab_ctrl.ts b/public/app/features/alerting/alert_tab_ctrl.ts index 938b24ebc21..5d23ebfec3a 100644 --- a/public/app/features/alerting/alert_tab_ctrl.ts +++ b/public/app/features/alerting/alert_tab_ctrl.ts @@ -79,7 +79,7 @@ export class AlertTabCtrl { getAlertHistory() { this.backendSrv.get(`/api/annotations?dashboardId=${this.panelCtrl.dashboard.id}&panelId=${this.panel.id}&limit=50`).then(res => { this.alertHistory = _.map(res, ah => { - ah.time = moment(ah.time).format('MMM D, YYYY HH:mm:ss'); + ah.time = this.dashboardSrv.getCurrent().formatDate(ah.time, 'MMM D, YYYY HH:mm:ss'); ah.stateModel = alertDef.getStateDisplayModel(ah.newState); ah.info = alertDef.getAlertAnnotationInfo(ah); return ah; diff --git a/public/app/features/alerting/notification_edit_ctrl.ts b/public/app/features/alerting/notification_edit_ctrl.ts index 768cc16266e..d1b855cbf3a 100644 --- a/public/app/features/alerting/notification_edit_ctrl.ts +++ b/public/app/features/alerting/notification_edit_ctrl.ts @@ -88,7 +88,7 @@ export class AlertNotificationEditCtrl { this.backendSrv.post(`/api/alert-notifications/test`, payload) .then(res => { - appEvents.emit('alert-succes', ['Test notification sent', '']); + appEvents.emit('alert-success', ['Test notification sent', '']); }); } } diff --git a/public/app/features/alerting/partials/alert_howto.html b/public/app/features/alerting/partials/alert_howto.html index c62906f69b8..054fd0eb6f3 100644 --- a/public/app/features/alerting/partials/alert_howto.html +++ b/public/app/features/alerting/partials/alert_howto.html @@ -19,7 +19,7 @@ Alerts are added and configured in the Alert Tab of any dashboard graph panel, letting you build and visualize an alert using existing queries.

- To persist you alert rule changes remember to save the dashboard. + To persist your alert rule changes remember to save the dashboard.

Go to Home Dashboard diff --git a/public/app/features/dashboard/dashnav/dashnav.html b/public/app/features/dashboard/dashnav/dashnav.html index f1615a0bd1c..bab27289aca 100644 --- a/public/app/features/dashboard/dashnav/dashnav.html +++ b/public/app/features/dashboard/dashnav/dashnav.html @@ -1,63 +1,95 @@ - - - - - diff --git a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts index afc0f4a5962..dbb2ffd6129 100644 --- a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts @@ -318,4 +318,70 @@ describe('CloudWatchDatasource', function() { expect(scenario.request.data.action).to.be('ListMetrics'); }); }); + + it('should caclculate the correct period', function () { + var hourSec = 60 * 60; + var daySec = hourSec * 24; + var start = 1483196400; + var testData: any[] = [ + [{ period: 60 }, { namespace: 'AWS/EC2' }, {}, start, start + 3600, (hourSec * 3), 60], + [{ period: null }, { namespace: 'AWS/EC2' }, {}, start, start + 3600, (hourSec * 3), 300], + [{ period: 60 }, { namespace: 'AWS/ELB' }, {}, start, start + 3600, (hourSec * 3), 60], + [{ period: null }, { namespace: 'AWS/ELB' }, {}, start, start + 3600, (hourSec * 3), 60], + [{ period: 1 }, { namespace: 'CustomMetricsNamespace' }, {}, start, start + 1440 - 1, (hourSec * 3 - 1), 1], + [{ period: 1 }, { namespace: 'CustomMetricsNamespace' }, {}, start, start + 3600, (hourSec * 3 - 1), 60], + [{ period: 60 }, { namespace: 'CustomMetricsNamespace' }, {}, start, start + 3600, (hourSec * 3), 60], + [{ period: null }, { namespace: 'CustomMetricsNamespace' }, {}, start, start + 3600, (hourSec * 3 - 1), 60], + [{ period: null }, { namespace: 'CustomMetricsNamespace' }, {}, start, start + 3600, (hourSec * 3), 60], + [{ period: null }, { namespace: 'CustomMetricsNamespace' }, {}, start, start + 3600, (daySec * 15), 60], + [{ period: null }, { namespace: 'CustomMetricsNamespace' }, {}, start, start + 3600, (daySec * 63), 300], + [{ period: null }, { namespace: 'CustomMetricsNamespace' }, {}, start, start + 3600, (daySec * 455), 3600] + ]; + for (let t of testData) { + let target = t[0]; + let query = t[1]; + let options = t[2]; + let start = t[3]; + let end = t[4]; + let now = start + t[5]; + let expected = t[6]; + let actual = ctx.ds.getPeriod(target, query, options, start, end, now); + expect(actual).to.be(expected); + } + }); + + describeMetricFindQuery('ec2_instance_attribute(us-east-1, Tags.Name, { "tag:team": [ "sysops" ] })', scenario => { + scenario.setup(() => { + scenario.requestResponse = { + Reservations: [ + { + Instances: [ + { + Tags: [ + { Key: 'InstanceId', Value: 'i-123456' }, + { Key: 'Name', Value: 'Sysops Dev Server' }, + { Key: 'env', Value: 'dev' }, + { Key: 'team', Value: 'sysops' } + ] + }, + { + Tags: [ + { Key: 'InstanceId', Value: 'i-789012' }, + { Key: 'Name', Value: 'Sysops Staging Server' }, + { Key: 'env', Value: 'staging' }, + { Key: 'team', Value: 'sysops' } + ] + } + ] + } + ] + }; + }); + + it('should return the "Name" tag for each instance', function() { + expect(scenario.result[0].text).to.be('Sysops Dev Server'); + expect(scenario.result[1].text).to.be('Sysops Staging Server'); + }); + }); + }); diff --git a/public/app/plugins/datasource/elasticsearch/bucket_agg.js b/public/app/plugins/datasource/elasticsearch/bucket_agg.js index b2cfc819579..5adaed173af 100644 --- a/public/app/plugins/datasource/elasticsearch/bucket_agg.js +++ b/public/app/plugins/datasource/elasticsearch/bucket_agg.js @@ -26,13 +26,21 @@ function (angular, _, queryDef) { var bucketAggs = $scope.target.bucketAggs; $scope.orderByOptions = []; - $scope.bucketAggTypes = queryDef.bucketAggTypes; - $scope.orderOptions = queryDef.orderOptions; - $scope.sizeOptions = queryDef.sizeOptions; + + $scope.getBucketAggTypes = function() { + return queryDef.bucketAggTypes; + }; + + $scope.getOrderOptions = function() { + return queryDef.orderOptions; + }; + + $scope.getSizeOptions = function() { + return queryDef.sizeOptions; + }; $rootScope.onAppEvent('elastic-query-updated', function() { $scope.validateModel(); - $scope.updateOrderByOptions(); }, $scope); $scope.init = function() { @@ -166,11 +174,10 @@ function (angular, _, queryDef) { $scope.toggleOptions = function() { $scope.showOptions = !$scope.showOptions; - $scope.updateOrderByOptions(); }; - $scope.updateOrderByOptions = function() { - $scope.orderByOptions = queryDef.getOrderByOptions($scope.target); + $scope.getOrderByOptions = function() { + return queryDef.getOrderByOptions($scope.target); }; $scope.getFieldsInternal = function() { diff --git a/public/app/plugins/datasource/elasticsearch/datasource.js b/public/app/plugins/datasource/elasticsearch/datasource.js index 08f42f2cf50..49398a894e7 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.js +++ b/public/app/plugins/datasource/elasticsearch/datasource.js @@ -323,23 +323,27 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes var buckets = res.responses[0].aggregations["1"].buckets; return _.map(buckets, function(bucket) { - return {text: bucket.key, value: bucket.key}; + return { + text: bucket.key_as_string || bucket.key, + value: bucket.key + }; }); }); }; this.metricFindQuery = function(query) { query = angular.fromJson(query); - query.query = templateSrv.replace(query.query || '*', {}, 'lucene'); - if (!query) { return $q.when([]); } if (query.find === 'fields') { + query.field = templateSrv.replace(query.field, {}, 'lucene'); return this.getFields(query); } + if (query.find === 'terms') { + query.query = templateSrv.replace(query.query || '*', {}, 'lucene'); return this.getTerms(query); } }; diff --git a/public/app/plugins/datasource/elasticsearch/partials/bucket_agg.html b/public/app/plugins/datasource/elasticsearch/partials/bucket_agg.html index 8c14f3ef356..a180f97c994 100644 --- a/public/app/plugins/datasource/elasticsearch/partials/bucket_agg.html +++ b/public/app/plugins/datasource/elasticsearch/partials/bucket_agg.html @@ -5,8 +5,22 @@ Then by - - + + + +
@@ -33,7 +47,13 @@
- + +
@@ -66,11 +86,23 @@
- + +
- + +
@@ -78,7 +110,13 @@
- + +