diff --git a/.bowerrc b/.bowerrc deleted file mode 100644 index 36643c6062c..00000000000 --- a/.bowerrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "directory": "public/vendor/" -} diff --git a/.jsfmtrc b/.jsfmtrc deleted file mode 100644 index 3ca3b4a3b50..00000000000 --- a/.jsfmtrc +++ /dev/null @@ -1,21 +0,0 @@ -{ - "preset" : "default", - - "lineBreak" : { - "before" : { - "VariableDeclarationWithoutInit" : 0, - }, - - "after": { - "AssignmentOperator": -1, - "ArgumentListArrayExpression": ">=1" - } - }, - - "whiteSpace" : { - "before" : { - }, - "after" : { - } - } -} diff --git a/CHANGELOG.md b/CHANGELOG.md index aa8d7425b3f..4760ee163bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,13 +15,20 @@ * **Graph**: Add support for local formating in axis. [#1395](https://github.com/grafana/grafana/issues/1395), thx [@m0nhawk](https://github.com/m0nhawk) * **Jaeger**: Add support for open tracing using jaeger in Grafana. [#9213](https://github.com/grafana/grafana/pull/9213) * **Unit types**: New date & time unit types added, useful in singlestat to show dates & times. [#3678](https://github.com/grafana/grafana/issues/3678), [#6710](https://github.com/grafana/grafana/issues/6710), [#2764](https://github.com/grafana/grafana/issues/6710) +* **CLI**: Make it possible to install plugins from any url [#5873](https://github.com/grafana/grafana/issues/5873) +* **Prometheus**: Add support for instant queries [#5765](https://github.com/grafana/grafana/issues/5765), thx [@mtanda](https://github.com/mtanda) -## Breaking changes -* **Metrics**: The metric structure for internal metrics about Grafana published to graphite has changed. This might break dashboards for internal metrics. +## Minor +* **SMTP**: Make it possible to set specific EHLO for smtp client. [#9319](https://github.com/grafana/grafana/issues/9319) +* **Dataproxy**: Allow grafan to renegotiate tls connection [#9250](https://github.com/grafana/grafana/issues/9250) -# 4.5.2 (unreleased) +# 4.5.2 (2017-09-22) ## Fixes +* **Graphite**: Fix for issues with jsonData & graphiteVersion null errors [#9258](https://github.com/grafana/grafana/issues/9258) +* **Graphite**: Fix for Grafana internal metrics to Graphite sending NaN values [#9279](https://github.com/grafana/grafana/issues/9279) +* **HTTP API**: Fix for HEAD method requests [#9307](https://github.com/grafana/grafana/issues/9307) +* **Templating**: Fix for duplicate template variable queries when refresh is set to time range change [#9185](https://github.com/grafana/grafana/issues/9185) * **Metrics**: dont write NaN values to graphite [#9279](https://github.com/grafana/grafana/issues/9279) # 4.5.1 (2017-09-15) @@ -29,6 +36,9 @@ ## Fixes * **MySQL**: Fixed issue with query editor not showing [#9247](https://github.com/grafana/grafana/issues/9247) +## Breaking changes +* **Metrics**: The metric structure for internal metrics about Grafana published to graphite has changed. This might break dashboards for internal metrics. + # 4.5.0 (2017-09-14) ## Fixes & Enhancements since beta1 diff --git a/bower.json b/bower.json deleted file mode 100644 index 62a8797e765..00000000000 --- a/bower.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "grafana", - "version": "2.0.2", - "homepage": "https://github.com/grafana/grafana", - "authors": [], - "license": "Apache 2.0", - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "public/vendor/", - "test", - "tests" - ], - "dependencies": { - "angular": "1.6.1", - "angular-route": "1.6.1", - "angular-mocks": "1.6.1", - "angular-sanitize": "1.6.1", - "angular-native-dragdrop": "1.2.2", - "angular-bindonce": "0.3.3", - "clipboard": "^1.5.16" - } -} diff --git a/conf/defaults.ini b/conf/defaults.ini index 5abd27e5c51..dfa6bf99017 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -318,6 +318,7 @@ key_file = skip_verify = false from_address = admin@grafana.localhost from_name = Grafana +ehlo_identity = [emails] welcome_email_on_sign_up = false @@ -476,6 +477,8 @@ provider = [external_image_storage.s3] bucket_url = +bucket = +region = access_key = secret_key = diff --git a/conf/sample.ini b/conf/sample.ini index 076b9306d84..89170000df6 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -295,6 +295,8 @@ ;skip_verify = false ;from_address = admin@grafana.localhost ;from_name = Grafana +# EHLO identity in SMTP dialog (defaults to instance_name) +;ehlo_identity = dashboard.example.com [emails] ;welcome_email_on_sign_up = false @@ -420,7 +422,8 @@ ;provider = [external_image_storage.s3] -;bucket_url = +;bucket = +;region = ;access_key = ;secret_key = diff --git a/docker/blocks/prometheus/prometheus.yml b/docker/blocks/prometheus/prometheus.yml index 0afb7968ce5..ae40dfdf067 100644 --- a/docker/blocks/prometheus/prometheus.yml +++ b/docker/blocks/prometheus/prometheus.yml @@ -17,20 +17,19 @@ alerting: - targets: - "127.0.0.1:9093" - -# A scrape configuration containing exactly one endpoint to scrape: -# Here it's Prometheus itself. scrape_configs: - # The job name is added as a label `job=` to any timeseries scraped from this config. - job_name: 'prometheus' - - # Override the global default and scrape targets from this job every 5 seconds. - scrape_interval: 10s - scrape_timeout: 10s - - # metrics_path defaults to '/metrics' - # scheme defaults to 'http'. - static_configs: - #- targets: ['localhost:9090', '172.17.0.1:9091', '172.17.0.1:9100', '172.17.0.1:9150'] - - targets: ['localhost:9090', '127.0.0.1:9091', '127.0.0.1:9100', '127.0.0.1:9150'] + - targets: ['localhost:9090'] + + - job_name: 'node_exporter' + static_configs: + - targets: ['127.0.0.1:9100'] + + - job_name: 'fake-data-gen' + static_configs: + - targets: ['127.0.0.1:9091'] + + - job_name: 'grafana' + static_configs: + - targets: ['127.0.0.1:3000'] diff --git a/docs/sources/features/panels/dashlist.md b/docs/sources/features/panels/dashlist.md index b6f7d21c1c8..0599fa77ff3 100644 --- a/docs/sources/features/panels/dashlist.md +++ b/docs/sources/features/panels/dashlist.md @@ -12,42 +12,23 @@ weight = 4 # Dashboard List Panel -The dashboard list panel allows you to display dynamic links to other dashboards. The list can be configured to use starred dashboards, a search query and/or dashboard tags. +{{< docs-imagebox img="/img/docs/v45/dashboard-list-panels.png" max-width= "800px" >}} - +The dashboard list panel allows you to display dynamic links to other dashboards. The list can be configured to use starred dashboards, recently viewed dashboards, a search query and/or dashboard tags. > On each dashboard load, the dashlist panel will re-query the dashboard list, always providing the most up to date results. -## Mode: Starred Dashboards +## Dashboard List Options -The `starred` dashboard selection displays starred dashboards, up to the number specified in the `Limit Number to` field, in alphabetical order. On dashboard load, the dashlist panel will re-query the favorites to appear in dashboard list panel, always providing the most up to date results. +{{< docs-imagebox img="/img/docs/v45/dashboard-list-options.png" max-width="600px" class="docs-image--no-shadow">}} - - - -## Mode: Search Dashboards - -The panel may be configured to search by either string query or tag(s). On dashboard load, the dashlist panel will re-query the dashboard list, always providing the most up to date results. - -To configure dashboard list in this manner, select `search` from the Mode select box. When selected, the Search Options section will appear. - - -Name | Description ------------- | ------------- -Mode | Set search or starred mode -Query | If in search mode specify the search query -Tags | if in search mode specify dashboard tags to search for -Limit number to | Specify the maximum number of dashboards - - -### Search by string - -To search by a string, enter a search query in the `Search Options: Query` field. Queries are case-insensitive, and partial values are accepted. - - -### Search by tag -To search by one or more tags, enter your selection in the `Search Options: Tags:` field. Note that existing tags will not appear as you type, and *are* case sensitive. To see a list of existing tags, you can always return to the dashboard, open the Dashboard Picker at the top and click `tags` link in the search bar. - +1. `Starred`: The starred dashboard selection displays starred dashboards in alphabetical order. +2. `Recently Viewed`: The recently viewed dashboard selection displays recently viewed dashboards in alphabetical order. +3. `Search`: The search dashboard selection displays dashboards by search query or tag(s). +4. `Show Headings`: When show headings is ticked the choosen list selection(Starred, Recently Viewed, Search) is shown as a heading. +5. `Max Items`: Max items set the maximum of items in a list. +6. `Query`: Here is where you enter your query you want to search by. Queries are case-insensitive, and partial values are accepted. +7. `Tags`: Here is where you enter your tag(s) you want to search by. Note that existing tags will not appear as you type, and *are* case sensitive. To see a list of existing tags, you can always return to the dashboard, open the Dashboard Picker at the top and click `tags` link in the search bar. > When multiple tags and strings appear, the dashboard list will display those matching ALL conditions. diff --git a/docs/sources/features/panels/graph.md b/docs/sources/features/panels/graph.md index 726a11bec3a..95be74c607e 100644 --- a/docs/sources/features/panels/graph.md +++ b/docs/sources/features/panels/graph.md @@ -13,14 +13,15 @@ weight = 1 The main panel in Grafana is simply named Graph. It provides a very rich set of graphing options. - +{{< docs-imagebox img="/img/docs/v45/graph_overview.png" class="docs-image--no-shadow" max-width= "900px" >}} -Clicking the title for a panel exposes a menu. The `edit` option opens additional configuration -options for the panel. +1. Clicking the title for a panel exposes a menu. The `edit` option opens additional configuration options for the panel. +2. Click to open color & axis selection. +3. Click to only show this series. Shift/Ctrl + click to hide series. ## General -![](/img/docs/v43/graph_general.png) +{{< docs-imagebox img="/img/docs/v43/graph_general.png" max-width= "900px" >}} The general tab allows customization of a panel's appearance and menu options. @@ -52,7 +53,7 @@ options. ## Axes -![](/img/docs/v43/graph_axes_grid_options.png) +{{< docs-imagebox img="/img/docs/v43/graph_axes_grid_options.png" max-width= "900px" >}} The Axes tab controls the display of axes, grids and legend. The ``Left Y`` and ``Right Y`` can be customized using: @@ -101,7 +102,7 @@ It is just the sum of all data points received by Grafana. ## Display styles -![](/img/docs/v43/graph_display_styles.png) +{{< docs-imagebox img="/img/docs/v43/graph_display_styles.png" max-width= "900px" >}} Display styles control visual properties of the graph. @@ -156,4 +157,6 @@ There is an option under Series overrides to draw lines as dashes. Set Dashes to ## Time Range -![](/img/docs/v2/graph_time_range.png) +The time range tab allows you to override the dashboard time range and specify a panel specific time. Either through a relative from now time option or through a timeshift. + +{{< docs-imagebox img="/img/docs/v45/graph-time-range.png" max-width= "900px" >}} diff --git a/docs/sources/features/panels/singlestat.md b/docs/sources/features/panels/singlestat.md index 7447f6c9b84..0d186ec5f3c 100644 --- a/docs/sources/features/panels/singlestat.md +++ b/docs/sources/features/panels/singlestat.md @@ -12,7 +12,7 @@ weight = 2 # Singlestat Panel -![](/img/docs/v1/singlestat_panel2.png) +{{< docs-imagebox img="/img/docs/v45/singlestat-panel.png" max-width="900px" >}} The Singlestat Panel allows you to show the one main summary stat of a SINGLE series. It reduces the series into a single number (by looking at the max, min, average, or sum of values in the series). Singlestat also provides thresholds to color the stat or the Panel background. It can also translate the single number into a text value, and show a sparkline summary of the series. @@ -20,11 +20,9 @@ The Singlestat Panel allows you to show the one main summary stat of a SINGLE se The singlestat panel has a normal query editor to allow you define your exact metric queries like many other Panels. Through the Options tab, you can access the Singlestat-specific functionality. - +{{< docs-imagebox img="/img/docs/v45/singlestat-value-options.png" class="docs-image--no-shadow" max-width= "900px" >}} -1. `Big Value`: Big Value refers to how we display the main stat for the Singlestat Panel. This is always a single value that is displayed in the Panel in between two strings, `Prefix` and `Suffix`. The single number is calculated by choosing a function (min,max,average,current,total) of your metric query. This functions reduces your query into a single numeric value. -2. `Font Size`: You can use this section to select the font size of the different texts in the Singlestat Panel, i.e. prefix, value and postfix. -3. `Values`: The Value fields let you set the function (min, max, average, current, total, first, delta, range) that your entire query is reduced into a single value with. You can also set the font size of the Value field and font-size (as a %) of the metric query that the Panel is configured with. This reduces the entire query into a single summary value that is displayed. +1. `Stats`: The Stats field let you set the function (min, max, average, current, total, first, delta, range) that your entire query is reduced into a single value with. This reduces the entire query into a single summary value that is displayed. * `min` - The smallest value in the series * `max` - The largest value in the series * `avg` - The average of all the non-null values in the series @@ -34,47 +32,64 @@ 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. `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. +2. `Prefix/Postfix`: The Prefix/Postfix fields let you define a custom label to appear *before/after* the value. The `$__name` variable can be used here to use the series name or alias from the metric query. +3. `Units`: Units are appended to the the Singlestat within the panel, and will respect the color and threshold settings for the value. +4. `Decimals`: The Decimal field allows you to override the automatic decimal precision, and set it explicitly. +5. `Font Size`: You can use this section to select the font size of the different texts in the Singlestat Panel, i.e. prefix, value and postfix. ### Coloring The coloring options of the Singlestat Panel config allow you to dynamically change the colors based on the Singlestat value. - +{{< docs-imagebox img="/img/docs/v45/singlestat-color-options.png" max-width="500px" class="docs-image--right docs-image--no-shadow">}} 1. `Background`: This checkbox applies the configured thresholds and colors to the entirety of the Singlestat Panel background. -2. `Value`: This checkbox applies the configured thresholds and colors to the summary stat. -3. `Thresholds`: Change the background and value colors dynamically within the panel, depending on the Singlestat value. The threshold field accepts **2 comma-separated** values which represent 3 ranges that correspond to the three colors directly to the right. For example: if the thresholds are 70, 90 then the first color represents < 70, the second color represents between 70 and 90 and the third color represents > 90. -4. `Colors`: Select a color and opacity +2. `Thresholds`: Change the background and value colors dynamically within the panel, depending on the Singlestat value. The threshold field accepts **2 comma-separated** values which represent 3 ranges that correspond to the three colors directly to the right. For example: if the thresholds are 70, 90 then the first color represents < 70, the second color represents between 70 and 90 and the third color represents > 90. +3. `Colors`: Select a color and opacity +4. `Value`: This checkbox applies the configured thresholds and colors to the summary stat. 5. `Invert order`: This link toggles the threshold color order.
For example: Green, Orange, Red () will become Red, Orange, Green (). ### Spark Lines Sparklines are a great way of seeing the historical data related to the summary stat, providing valuable context at a glance. Sparklines act differently than traditional Graph Panels and do not include x or y axis, coordinates, a legend, or ability to interact with the graph. - +{{< docs-imagebox img="/img/docs/v45/singlestat-spark-options.png" max-width="500px" class="docs-image--right docs-image--no-shadow">}} 1. `Show`: The show checkbox will toggle whether the spark line is shown in the Panel. When unselected, only the Singlestat value will appear. -2. `Background`: Check if you want the sparklines to take up the full panel width, or uncheck if they should be below the main Singlestat value. +2. `Full Height`: Check if you want the sparklines to take up the full panel height, or uncheck if they should be below the main Singlestat value. 3. `Line Color`: This color selection applies to the color of the sparkline itself. 4. `Fill Color`: This color selection applies to the area below the sparkline. +
+ > ***Pro-tip:*** Reduce the opacity on fill colors for nice looking panels. +### Gauge + +Gauges gives a clear picture of how high a value is in it's context. It's a great way to see if a value is close to the thresholds. The gauge uses the colors set in the color options. + +{{< docs-imagebox img="/img/docs/v45/singlestat-gauge-options.png" max-width="500px" class="docs-image--right docs-image--no-shadow">}} + +1. `Show`: The show checkbox will toggle wether the gauge is shown in the panel. When unselected, only the Singlestat value will appear. +2. `Min/Max`: This sets the start and end point for the gauge. +3. `Threshold Labels`: Check if you want to show the threshold labels. Thresholds are set in the color options. +4. `Threshold Markers`: Check if you want to have a second meter showing the thresholds. + +
+ ### Value to text mapping +{{< docs-imagebox img="/img/docs/v45/singlestat-value-mapping.png" class="docs-image--right docs-image--no-shadow">}} + Value to text mapping allows you to translate the value of the summary stat into explicit text. The text will respect all styling, thresholds and customization defined for the value. This can be useful to translate the number of the main Singlestat value into a context-specific human-readable word or message. - +
## Troubleshooting ### Multiple Series Error - - +{{< docs-imagebox img="/img/docs/v45/singelstat-multiple-series-error.png" class="docs-image--right docs-image--no-shadow">}} Grafana 2.5 introduced stricter checking for multiple-series on singlestat panels. In previous versions, the panel logic did not verify that only a single series was used, and instead, displayed the first series encountered. Depending on your data source, this could have lead to inconsistent data being shown and/or a general confusion about which metric was being displayed. diff --git a/docs/sources/http_api/admin.md b/docs/sources/http_api/admin.md index d7e5d3f8b7c..55bec79c7f8 100644 --- a/docs/sources/http_api/admin.md +++ b/docs/sources/http_api/admin.md @@ -161,6 +161,7 @@ Only works with Basic Authentication (username and password). See [introduction] "enabled":"false", "from_address":"admin@grafana.localhost", "from_name":"Grafana", + "ehlo_identity":"dashboard.example.com", "host":"localhost:25", "key_file":"", "password":"************", diff --git a/docs/sources/installation/configuration.md b/docs/sources/installation/configuration.md index b8593b681df..85ff1026c10 100644 --- a/docs/sources/installation/configuration.md +++ b/docs/sources/installation/configuration.md @@ -593,6 +593,9 @@ Address used when sending out emails, defaults to `admin@grafana.localhost` ### from_name Name to be used when sending out emails, defaults to `Grafana` +### ehlo_identity +Name to be used as client identity for EHLO in SMTP dialog, defaults to instance_name. + ## [log] ### mode @@ -648,12 +651,16 @@ These options control how images should be made public so they can be shared on You can choose between (s3, webdav, gcs). If left empty Grafana will ignore the upload action. ## [external_image_storage.s3] +### bucket +Bucket name for S3. e.g. grafana.snapshot +### region +Region name for S3. e.g. 'us-east-1', 'cn-north-1', etc ### bucket_url +(for backward compatibility, only works when no bucket or region are configured) Bucket URL for S3. AWS region can be specified within URL or defaults to 'us-east-1', e.g. - http://grafana.s3.amazonaws.com/ - https://grafana.s3-ap-southeast-2.amazonaws.com/ -- https://grafana.s3-cn-north-1.amazonaws.com.cn ### access_key Access key. e.g. AAAAAAAAAAAAAAAAAAAA diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index 93915f52824..330e3171e86 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.5.1_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.5.1_amd64.deb) +Stable for Debian-based Linux | [grafana_4.5.2_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.5.2_amd64.deb) @@ -26,18 +26,18 @@ installation. ```bash -wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.5.1_amd64.deb +wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.5.2_amd64.deb sudo apt-get install -y adduser libfontconfig -sudo dpkg -i grafana_4.5.1_amd64.deb +sudo dpkg -i grafana_4.5.2_amd64.deb ``` diff --git a/docs/sources/installation/mac.md b/docs/sources/installation/mac.md index 912a16c66e8..a65c663f398 100644 --- a/docs/sources/installation/mac.md +++ b/docs/sources/installation/mac.md @@ -43,3 +43,35 @@ To upgrade grafana if you've installed from HEAD: ``` brew reinstall --HEAD grafana/grafana/grafana ``` + +### Starting Grafana + +To start Grafana using homebrew services first make sure homebrew/services is installed. + +``` +brew tap homebrew/services +``` + +Then start Grafana using: + +``` +brew services start grafana +``` + + +### Configuration + +The Configuration file should be located at `/usr/local/etc/grafana/grafana.ini`. + +### Logs + +The log file should be located at `/usr/local/var/log/grafana/grafana.log`. + +### Plugins + +If you want to manually install a plugin place it here: `/usr/local/var/lib/grafana/plugins`. + +### Database + +The default sqlite database is located at `/usr/local/var/lib/grafana` + diff --git a/docs/sources/installation/rpm.md b/docs/sources/installation/rpm.md index 1eac82dcfb3..30b017314c6 100644 --- a/docs/sources/installation/rpm.md +++ b/docs/sources/installation/rpm.md @@ -15,7 +15,7 @@ weight = 2 Description | Download ------------ | ------------- -Stable for CentOS / Fedora / OpenSuse / Redhat Linux | [4.5.1 (x86-64 rpm)](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.5.1-1.x86_64.rpm) +Stable for CentOS / Fedora / OpenSuse / Redhat Linux | [4.5.2 (x86-64 rpm)](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.5.2-1.x86_64.rpm) @@ -26,19 +26,19 @@ installation. You can install Grafana using Yum directly. - $ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.5.1-1.x86_64.rpm + $ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.5.2-1.x86_64.rpm Or install manually using `rpm`. #### On CentOS / Fedora / Redhat: - $ wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.5.1-1.x86_64.rpm + $ wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.5.2-1.x86_64.rpm $ sudo yum install initscripts fontconfig - $ sudo rpm -Uvh grafana-4.5.1-1.x86_64.rpm + $ sudo rpm -Uvh grafana-4.5.2-1.x86_64.rpm #### On OpenSuse: - $ sudo rpm -i --nodeps grafana-4.5.1-1.x86_64.rpm + $ sudo rpm -i --nodeps grafana-4.5.2-1.x86_64.rpm ## Install via YUM Repository diff --git a/docs/sources/installation/windows.md b/docs/sources/installation/windows.md index ad1090cecf0..ea9614c01be 100644 --- a/docs/sources/installation/windows.md +++ b/docs/sources/installation/windows.md @@ -13,7 +13,7 @@ weight = 3 Description | Download ------------ | ------------- -Latest stable package for Windows | [grafana.4.5.1.windows-x64.zip](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.5.1.windows-x64.zip) +Latest stable package for Windows | [grafana.4.5.2.windows-x64.zip](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.5.2.windows-x64.zip) Read [Upgrading Grafana]({{< relref "installation/upgrading.md" >}}) for tips and guidance on updating an existing installation. diff --git a/docs/sources/plugins/installation.md b/docs/sources/plugins/installation.md index 977abb055f1..27f6f583d9a 100644 --- a/docs/sources/plugins/installation.md +++ b/docs/sources/plugins/installation.md @@ -72,6 +72,11 @@ The Download URL from Grafana.com API is in this form: `https://grafana.com/api/plugins//versions//download` +You can specify a local URL by using the `--pluginUrl` option. +``` +grafana-cli --pluginUrl https://nexus.company.com/grafana/plugins/-.zip plugins install +``` + To manually install a Plugin via the Grafana.com API: 1. Find the plugin you want to download, the plugin id can be found on the Installation Tab on the plugin's page on Grafana.com. In this example, the plugin id is `jdbranham-diagram-panel`: diff --git a/docs/sources/reference/keyboard_shortcuts.md b/docs/sources/reference/keyboard_shortcuts.md deleted file mode 100644 index edd2e85f43b..00000000000 --- a/docs/sources/reference/keyboard_shortcuts.md +++ /dev/null @@ -1,34 +0,0 @@ -+++ -title = "Keyboard shortcuts" -keywords = ["grafana", "dashboard", "documentation", "shortcuts"] -type = "docs" -[menu.docs] -parent = "dashboard_features" -weight = 8 -+++ - -# Keyboard Shortcuts - -No mouse? No problem. Grafana has extensive keyboard shortcuts to allow you to navigate throughout the interface. This comes in especially handy when dealing with dealing with single-purpose machines powering on-wall displays that may not have a mouse available. - -## Dashboard Keyboard Shortcuts - -Press `Shift`+`?` to open the keyboard shortcut dialog from anywhere within the dashboard views. - - - - -|Shortcut|Action| -|---|---| -|`Esc`|Exit fullscreen edit/view mode, close search or any editor view| -|`F`|Open dashboard search view (also contains import/playlist controls)| -|`R`|Refresh (Fetches new data and rerenders panels)| -|`CTRL`+`S`|Save dashboard| -|`CTRL`+`H`|Hide row controls| -|`CTRL`+`Z`|Zoom out| -|`CTRL`+`O`|Enable/Disable shared graph crosshair| - - -**Note**: Grafana keyboard shortcuts are the same across operating system. - -Have a suggestion for a new keyboard shortcut? Let us know. diff --git a/latest.json b/latest.json index db7677517e0..fa72bc4fadf 100644 --- a/latest.json +++ b/latest.json @@ -1,4 +1,4 @@ { - "stable": "4.4.1", - "testing": "4.4.1" + "stable": "4.5.2", + "testing": "4.5.2" } diff --git a/package.json b/package.json index a6ee71e8169..c029ec32a03 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,13 @@ "dependencies": { "@types/enzyme": "^2.8.8", "ace-builds": "^1.2.8", + "angular": "^1.6.6", + "angular-bindonce": "^0.3.1", + "angular-mocks": "^1.6.6", + "angular-native-dragdrop": "^1.2.2", + "angular-route": "^1.6.6", + "angular-sanitize": "^1.6.6", + "clipboard": "^1.7.1", "eventemitter3": "^2.0.2", "gaze": "^1.1.2", "gridstack": "https://github.com/grafana/gridstack.js#grafana", diff --git a/packaging/publish/publish_both.sh b/packaging/publish/publish_both.sh index 38abc022e55..0a76851f6fa 100755 --- a/packaging/publish/publish_both.sh +++ b/packaging/publish/publish_both.sh @@ -1,5 +1,5 @@ #! /usr/bin/env bash -version=4.5.1 +version=4.5.2 wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_${version}_amd64.deb @@ -8,15 +8,15 @@ package_cloud push grafana/stable/debian/wheezy grafana_${version}_amd64.deb package_cloud push grafana/stable/debian/stretch grafana_${version}_amd64.deb package_cloud push grafana/testing/debian/jessie grafana_${version}_amd64.deb -package_cloud push grafana/testing/debian/wheezy grafana_${version}_amd64.deb -package_cloud push grafana/testing/debian/stretch grafana_${version}_amd64.deb +package_cloud push grafana/testing/debian/wheezy grafana_${version}_amd64.deb --verbose +package_cloud push grafana/testing/debian/stretch grafana_${version}_amd64.deb --verbose wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-${version}-1.x86_64.rpm -package_cloud push grafana/testing/el/6 grafana-${version}-1.x86_64.rpm -package_cloud push grafana/testing/el/7 grafana-${version}-1.x86_64.rpm +package_cloud push grafana/testing/el/6 grafana-${version}-1.x86_64.rpm --verbose +package_cloud push grafana/testing/el/7 grafana-${version}-1.x86_64.rpm --verbose -package_cloud push grafana/stable/el/7 grafana-${version}-1.x86_64.rpm -package_cloud push grafana/stable/el/6 grafana-${version}-1.x86_64.rpm +package_cloud push grafana/stable/el/7 grafana-${version}-1.x86_64.rpm --verbose +package_cloud push grafana/stable/el/6 grafana-${version}-1.x86_64.rpm --verbose rm grafana*.{deb,rpm} diff --git a/pkg/api/app_routes.go b/pkg/api/app_routes.go index 812ea667eee..a7638879a93 100644 --- a/pkg/api/app_routes.go +++ b/pkg/api/app_routes.go @@ -17,8 +17,11 @@ import ( ) var pluginProxyTransport = &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - Proxy: http.ProxyFromEnvironment, + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + Renegotiation: tls.RenegotiateFreelyAsClient, + }, + Proxy: http.ProxyFromEnvironment, Dial: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, diff --git a/pkg/api/metrics.go b/pkg/api/metrics.go index 2b3e3c09cd4..717565cd4a1 100644 --- a/pkg/api/metrics.go +++ b/pkg/api/metrics.go @@ -43,7 +43,7 @@ func QueryMetrics(c *middleware.Context, reqDto dtos.MetricRequest) Response { }) } - resp, err := tsdb.HandleRequest(context.Background(), request) + resp, err := tsdb.HandleRequest(context.Background(), dsQuery.Result, request) if err != nil { return ApiError(500, "Metric request error", err) } @@ -100,16 +100,17 @@ func GetTestDataRandomWalk(c *middleware.Context) Response { timeRange := tsdb.NewTimeRange(from, to) request := &tsdb.TsdbQuery{TimeRange: timeRange} + dsInfo := &models.DataSource{Type: "grafana-testdata-datasource"} request.Queries = append(request.Queries, &tsdb.Query{ RefId: "A", IntervalMs: intervalMs, Model: simplejson.NewFromAny(&util.DynMap{ "scenario": "random_walk", }), - DataSource: &models.DataSource{Type: "grafana-testdata-datasource"}, + DataSource: dsInfo, }) - resp, err := tsdb.HandleRequest(context.Background(), request) + resp, err := tsdb.HandleRequest(context.Background(), dsInfo, request) if err != nil { return ApiError(500, "Metric request error", err) } diff --git a/pkg/cmd/grafana-cli/commands/command_line.go b/pkg/cmd/grafana-cli/commands/command_line.go index ce5d04c1bb5..d487aff8aaa 100644 --- a/pkg/cmd/grafana-cli/commands/command_line.go +++ b/pkg/cmd/grafana-cli/commands/command_line.go @@ -19,6 +19,7 @@ type CommandLine interface { PluginDirectory() string RepoDirectory() string + PluginURL() string } type contextCommandLine struct { @@ -44,3 +45,7 @@ func (c *contextCommandLine) PluginDirectory() string { func (c *contextCommandLine) RepoDirectory() string { return c.GlobalString("repo") } + +func (c *contextCommandLine) PluginURL() string { + return c.GlobalString("pluginUrl") +} diff --git a/pkg/cmd/grafana-cli/commands/commandstest/fake_commandLine.go b/pkg/cmd/grafana-cli/commands/commandstest/fake_commandLine.go index 8366e0feb15..f7afc57fda2 100644 --- a/pkg/cmd/grafana-cli/commands/commandstest/fake_commandLine.go +++ b/pkg/cmd/grafana-cli/commands/commandstest/fake_commandLine.go @@ -101,3 +101,7 @@ func (fcli *FakeCommandLine) RepoDirectory() string { func (fcli *FakeCommandLine) PluginDirectory() string { return fcli.GlobalString("pluginsDir") } + +func (fcli *FakeCommandLine) PluginURL() string { + return fcli.GlobalString("pluginUrl") +} diff --git a/pkg/cmd/grafana-cli/commands/install_command.go b/pkg/cmd/grafana-cli/commands/install_command.go index 1f391ea10b3..a1b249d9c81 100644 --- a/pkg/cmd/grafana-cli/commands/install_command.go +++ b/pkg/cmd/grafana-cli/commands/install_command.go @@ -58,37 +58,39 @@ func installCommand(c CommandLine) error { } func InstallPlugin(pluginName, version string, c CommandLine) error { - plugin, err := s.GetPlugin(pluginName, c.RepoDirectory()) pluginFolder := c.PluginDirectory() - if err != nil { - return err + downloadURL := c.PluginURL() + if downloadURL == "" { + plugin, err := s.GetPlugin(pluginName, c.RepoDirectory()) + if err != nil { + return err + } + + v, err := SelectVersion(plugin, version) + if err != nil { + return err + } + + if version == "" { + version = v.Version + } + downloadURL = fmt.Sprintf("%s/%s/versions/%s/download", + c.GlobalString("repo"), + pluginName, + version) } - v, err := SelectVersion(plugin, version) - if err != nil { - return err - } - - if version == "" { - version = v.Version - } - - downloadURL := fmt.Sprintf("%s/%s/versions/%s/download", - c.GlobalString("repo"), - pluginName, - version) - - logger.Infof("installing %v @ %v\n", plugin.Id, version) + logger.Infof("installing %v @ %v\n", pluginName, version) logger.Infof("from url: %v\n", downloadURL) logger.Infof("into: %v\n", pluginFolder) logger.Info("\n") - err = downloadFile(plugin.Id, pluginFolder, downloadURL) + err := downloadFile(pluginName, pluginFolder, downloadURL) if err != nil { return err } - logger.Infof("%s Installed %s successfully \n", color.GreenString("✔"), plugin.Id) + logger.Infof("%s Installed %s successfully \n", color.GreenString("✔"), pluginName) res, _ := s.ReadPlugin(pluginFolder, pluginName) for _, v := range res.Dependencies.Plugins { diff --git a/pkg/cmd/grafana-cli/main.go b/pkg/cmd/grafana-cli/main.go index e586e606fb5..73548c3b159 100644 --- a/pkg/cmd/grafana-cli/main.go +++ b/pkg/cmd/grafana-cli/main.go @@ -38,6 +38,12 @@ func main() { Value: "https://grafana.com/api/plugins", EnvVar: "GF_PLUGIN_REPO", }, + cli.StringFlag{ + Name: "pluginUrl", + Usage: "Full url to the plugin zip file instead of downloading the plugin from grafana.com/api", + Value: "", + EnvVar: "GF_PLUGIN_URL", + }, cli.BoolFlag{ Name: "debug, d", Usage: "enable debug logging", diff --git a/pkg/cmd/grafana-server/main.go b/pkg/cmd/grafana-server/main.go index 96fb3f2e9a1..8f90da93177 100644 --- a/pkg/cmd/grafana-server/main.go +++ b/pkg/cmd/grafana-server/main.go @@ -14,6 +14,7 @@ import ( "net/http" _ "net/http/pprof" + "github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" @@ -29,7 +30,7 @@ import ( _ "github.com/grafana/grafana/pkg/tsdb/testdata" ) -var version = "4.1.0" +var version = "4.6.0" var commit = "NA" var buildstamp string var build_date string @@ -80,6 +81,8 @@ func main() { setting.BuildCommit = commit setting.BuildStamp = buildstampInt64 + metrics.M_Grafana_Version.WithLabelValues(version).Set(1) + server := NewGrafanaServer() server.Start() } diff --git a/pkg/components/imguploader/imguploader.go b/pkg/components/imguploader/imguploader.go index 32058e6cc2d..1feeb6f07c3 100644 --- a/pkg/components/imguploader/imguploader.go +++ b/pkg/components/imguploader/imguploader.go @@ -28,15 +28,21 @@ func NewImageUploader() (ImageUploader, error) { return nil, err } + bucket := s3sec.Key("bucket").MustString("") + region := s3sec.Key("region").MustString("") bucketUrl := s3sec.Key("bucket_url").MustString("") accessKey := s3sec.Key("access_key").MustString("") secretKey := s3sec.Key("secret_key").MustString("") - info, err := getRegionAndBucketFromUrl(bucketUrl) - if err != nil { - return nil, err + if bucket == "" || region == "" { + info, err := getRegionAndBucketFromUrl(bucketUrl) + if err != nil { + return nil, err + } + bucket = info.bucket + region = info.region } - return NewS3Uploader(info.region, info.bucket, "public-read", accessKey, secretKey), nil + return NewS3Uploader(region, bucket, "public-read", accessKey, secretKey), nil case "webdav": webdavSec, err := setting.Cfg.GetSection("external_image_storage.webdav") if err != nil { diff --git a/pkg/components/imguploader/s3uploader.go b/pkg/components/imguploader/s3uploader.go index a8694c0389c..860bb1a1abd 100644 --- a/pkg/components/imguploader/s3uploader.go +++ b/pkg/components/imguploader/s3uploader.go @@ -9,6 +9,7 @@ import ( "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds" "github.com/aws/aws-sdk-go/aws/ec2metadata" + "github.com/aws/aws-sdk-go/aws/endpoints" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" "github.com/grafana/grafana/pkg/log" @@ -54,8 +55,10 @@ func (u *S3Uploader) Upload(ctx context.Context, imageDiskPath string) (string, Credentials: creds, } + s3_endpoint, _ := endpoints.DefaultResolver().EndpointFor("s3", u.region) key := util.GetRandomString(20) + ".png" - log.Debug("Uploading image to s3", "bucket = ", u.bucket, ", key = ", key) + image_url := s3_endpoint.URL + "/" + u.bucket + "/" + key + log.Debug("Uploading image to s3", "url = ", image_url) file, err := os.Open(imageDiskPath) if err != nil { @@ -78,10 +81,5 @@ func (u *S3Uploader) Upload(ctx context.Context, imageDiskPath string) (string, if err != nil { return "", err } - - if u.region == "us-east-1" { - return "https://" + u.bucket + ".s3.amazonaws.com/" + key, nil - } else { - return "https://" + u.bucket + ".s3-" + u.region + ".amazonaws.com/" + key, nil - } + return image_url, nil } diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index fef1fe26f6c..4b155ae3208 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -56,6 +56,7 @@ var ( M_StatTotal_Users prometheus.Gauge M_StatTotal_Orgs prometheus.Gauge M_StatTotal_Playlists prometheus.Gauge + M_Grafana_Version *prometheus.GaugeVec ) func init() { @@ -263,6 +264,13 @@ func init() { Help: "total amount of playlists", Namespace: exporterName, }) + + M_Grafana_Version = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: "info", + Help: "Information about the Grafana", + Namespace: exporterName, + }, []string{"version"}) + } func initMetricVars(settings *MetricSettings) { @@ -298,7 +306,8 @@ func initMetricVars(settings *MetricSettings) { M_StatTotal_Dashboards, M_StatTotal_Users, M_StatTotal_Orgs, - M_StatTotal_Playlists) + M_StatTotal_Playlists, + M_Grafana_Version) go instrumentationLoop(settings) } diff --git a/pkg/models/datasource_cache.go b/pkg/models/datasource_cache.go index e32c0ac9e7c..4a459c2783b 100644 --- a/pkg/models/datasource_cache.go +++ b/pkg/models/datasource_cache.go @@ -48,6 +48,7 @@ func (ds *DataSource) GetHttpTransport() (*http.Transport, error) { transport := &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, + Renegotiation: tls.RenegotiateFreelyAsClient, }, Proxy: http.ProxyFromEnvironment, Dial: (&net.Dialer{ diff --git a/pkg/services/alerting/conditions/query.go b/pkg/services/alerting/conditions/query.go index 417b3c96f10..d499c5e8532 100644 --- a/pkg/services/alerting/conditions/query.go +++ b/pkg/services/alerting/conditions/query.go @@ -112,7 +112,7 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange * req := c.getRequestForAlertRule(getDsInfo.Result, timeRange) result := make(tsdb.TimeSeriesSlice, 0) - resp, err := c.HandleRequest(context.Ctx, req) + resp, err := c.HandleRequest(context.Ctx, getDsInfo.Result, req) if err != nil { if err == gocontext.DeadlineExceeded { return nil, fmt.Errorf("Alert execution exceeded the timeout") diff --git a/pkg/services/alerting/conditions/query_test.go b/pkg/services/alerting/conditions/query_test.go index c5a15adf45b..0ea6470bc2d 100644 --- a/pkg/services/alerting/conditions/query_test.go +++ b/pkg/services/alerting/conditions/query_test.go @@ -168,7 +168,7 @@ func (ctx *queryConditionTestContext) exec() (*alerting.ConditionResult, error) ctx.condition = condition - condition.HandleRequest = func(context context.Context, req *tsdb.TsdbQuery) (*tsdb.Response, error) { + condition.HandleRequest = func(context context.Context, dsInfo *m.DataSource, req *tsdb.TsdbQuery) (*tsdb.Response, error) { return &tsdb.Response{ Results: map[string]*tsdb.QueryResult{ "A": {Series: ctx.series}, diff --git a/pkg/services/notifications/mailer.go b/pkg/services/notifications/mailer.go index df9f1138b15..7fbf39ee41d 100644 --- a/pkg/services/notifications/mailer.go +++ b/pkg/services/notifications/mailer.go @@ -101,7 +101,11 @@ func createDialer() (*gomail.Dialer, error) { d := gomail.NewDialer(host, iPort, setting.Smtp.User, setting.Smtp.Password) d.TLSConfig = tlsconfig - d.LocalName = setting.InstanceName + if setting.Smtp.EhloIdentity != "" { + d.LocalName = setting.Smtp.EhloIdentity + } else { + d.LocalName = setting.InstanceName + } return d, nil } diff --git a/pkg/setting/setting_smtp.go b/pkg/setting/setting_smtp.go index db065070620..9d8b8a529a5 100644 --- a/pkg/setting/setting_smtp.go +++ b/pkg/setting/setting_smtp.go @@ -1,15 +1,16 @@ package setting type SmtpSettings struct { - Enabled bool - Host string - User string - Password string - CertFile string - KeyFile string - FromAddress string - FromName string - SkipVerify bool + Enabled bool + Host string + User string + Password string + CertFile string + KeyFile string + FromAddress string + FromName string + EhloIdentity string + SkipVerify bool SendWelcomeEmailOnSignUp bool TemplatesPattern string @@ -25,6 +26,7 @@ func readSmtpSettings() { Smtp.KeyFile = sec.Key("key_file").String() Smtp.FromAddress = sec.Key("from_address").String() Smtp.FromName = sec.Key("from_name").String() + Smtp.EhloIdentity = sec.Key("ehlo_identity").String() Smtp.SkipVerify = sec.Key("skip_verify").MustBool(false) emails := Cfg.Section("emails") diff --git a/pkg/tsdb/fake_test.go b/pkg/tsdb/fake_test.go index ab1bc944eb8..572b3c77ff3 100644 --- a/pkg/tsdb/fake_test.go +++ b/pkg/tsdb/fake_test.go @@ -20,18 +20,18 @@ func NewFakeExecutor(dsInfo *models.DataSource) (*FakeExecutor, error) { }, nil } -func (e *FakeExecutor) Query(ctx context.Context, dsInfo *models.DataSource, context *TsdbQuery) *BatchResult { - result := &BatchResult{QueryResults: make(map[string]*QueryResult)} +func (e *FakeExecutor) Query(ctx context.Context, dsInfo *models.DataSource, context *TsdbQuery) (*Response, error) { + result := &Response{Results: make(map[string]*QueryResult)} for _, query := range context.Queries { if results, has := e.results[query.RefId]; has { - result.QueryResults[query.RefId] = results + result.Results[query.RefId] = results } if testFunc, has := e.resultsFn[query.RefId]; has { - result.QueryResults[query.RefId] = testFunc(context) + result.Results[query.RefId] = testFunc(context) } } - return result + return result, nil } func (e *FakeExecutor) Return(refId string, series TimeSeriesSlice) { diff --git a/pkg/tsdb/graphite/graphite.go b/pkg/tsdb/graphite/graphite.go index 273bf826de5..7cadf055ff6 100644 --- a/pkg/tsdb/graphite/graphite.go +++ b/pkg/tsdb/graphite/graphite.go @@ -37,8 +37,8 @@ func init() { tsdb.RegisterTsdbQueryEndpoint("graphite", NewGraphiteExecutor) } -func (e *GraphiteExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) *tsdb.BatchResult { - result := &tsdb.BatchResult{} +func (e *GraphiteExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) { + result := &tsdb.Response{} from := "-" + formatTimeRange(tsdbQuery.TimeRange.From) until := formatTimeRange(tsdbQuery.TimeRange.To) @@ -67,14 +67,12 @@ func (e *GraphiteExecutor) Query(ctx context.Context, dsInfo *models.DataSource, req, err := e.createRequest(dsInfo, formData) if err != nil { - result.Error = err - return result + return nil, err } httpClient, err := dsInfo.GetHttpClient() if err != nil { - result.Error = err - return result + return nil, err } span, ctx := opentracing.StartSpanFromContext(ctx, "graphite query") @@ -90,17 +88,15 @@ func (e *GraphiteExecutor) Query(ctx context.Context, dsInfo *models.DataSource, res, err := ctxhttp.Do(ctx, httpClient, req) if err != nil { - result.Error = err - return result + return nil, err } data, err := e.parseResponse(res) if err != nil { - result.Error = err - return result + return nil, err } - result.QueryResults = make(map[string]*tsdb.QueryResult) + result.Results = make(map[string]*tsdb.QueryResult) queryRes := tsdb.NewQueryResult() for _, series := range data { @@ -114,8 +110,8 @@ func (e *GraphiteExecutor) Query(ctx context.Context, dsInfo *models.DataSource, } } - result.QueryResults["A"] = queryRes - return result + result.Results["A"] = queryRes + return result, nil } func (e *GraphiteExecutor) parseResponse(res *http.Response) ([]TargetResponseDTO, error) { diff --git a/pkg/tsdb/influxdb/influxdb.go b/pkg/tsdb/influxdb/influxdb.go index e79e7b7790c..6100d3b401e 100644 --- a/pkg/tsdb/influxdb/influxdb.go +++ b/pkg/tsdb/influxdb/influxdb.go @@ -39,17 +39,17 @@ func init() { tsdb.RegisterTsdbQueryEndpoint("influxdb", NewInfluxDBExecutor) } -func (e *InfluxDBExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) *tsdb.BatchResult { - result := &tsdb.BatchResult{} +func (e *InfluxDBExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) { + result := &tsdb.Response{} query, err := e.getQuery(dsInfo, tsdbQuery.Queries, tsdbQuery) if err != nil { - return result.WithError(err) + return nil, err } rawQuery, err := query.Build(tsdbQuery) if err != nil { - return result.WithError(err) + return nil, err } if setting.Env == setting.DEV { @@ -58,21 +58,21 @@ func (e *InfluxDBExecutor) Query(ctx context.Context, dsInfo *models.DataSource, req, err := e.createRequest(dsInfo, rawQuery) if err != nil { - return result.WithError(err) + return nil, err } httpClient, err := dsInfo.GetHttpClient() if err != nil { - return result.WithError(err) + return nil, err } resp, err := ctxhttp.Do(ctx, httpClient, req) if err != nil { - return result.WithError(err) + return nil, err } if resp.StatusCode/100 != 2 { - return result.WithError(fmt.Errorf("Influxdb returned statuscode invalid status code: %v", resp.Status)) + return nil, fmt.Errorf("Influxdb returned statuscode invalid status code: %v", resp.Status) } var response Response @@ -82,17 +82,17 @@ func (e *InfluxDBExecutor) Query(ctx context.Context, dsInfo *models.DataSource, err = dec.Decode(&response) if err != nil { - return result.WithError(err) + return nil, err } if response.Err != nil { - return result.WithError(response.Err) + return nil, response.Err } - result.QueryResults = make(map[string]*tsdb.QueryResult) - result.QueryResults["A"] = e.ResponseParser.Parse(&response, query) + result.Results = make(map[string]*tsdb.QueryResult) + result.Results["A"] = e.ResponseParser.Parse(&response, query) - return result + return result, nil } func (e *InfluxDBExecutor) getQuery(dsInfo *models.DataSource, queries []*tsdb.Query, context *tsdb.TsdbQuery) (*Query, error) { diff --git a/pkg/tsdb/models.go b/pkg/tsdb/models.go index 2fa2ed06c95..cbda7c97515 100644 --- a/pkg/tsdb/models.go +++ b/pkg/tsdb/models.go @@ -22,24 +22,8 @@ type Query struct { } type Response struct { - BatchTimings []*BatchTiming `json:"timings"` - Results map[string]*QueryResult `json:"results"` - Message string `json:"message,omitempty"` -} - -type BatchTiming struct { - TimeElapsed int64 -} - -type BatchResult struct { - Error error - QueryResults map[string]*QueryResult - Timings *BatchTiming -} - -func (br *BatchResult) WithError(err error) *BatchResult { - br.Error = err - return br + Results map[string]*QueryResult `json:"results"` + Message string `json:"message,omitempty"` } type QueryResult struct { diff --git a/pkg/tsdb/mysql/mysql.go b/pkg/tsdb/mysql/mysql.go index b1e7e916787..19aa50096b1 100644 --- a/pkg/tsdb/mysql/mysql.go +++ b/pkg/tsdb/mysql/mysql.go @@ -85,9 +85,9 @@ func (e *MysqlExecutor) initEngine(dsInfo *models.DataSource) error { return nil } -func (e *MysqlExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) *tsdb.BatchResult { - result := &tsdb.BatchResult{ - QueryResults: make(map[string]*tsdb.QueryResult), +func (e *MysqlExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) { + result := &tsdb.Response{ + Results: make(map[string]*tsdb.QueryResult), } macroEngine := NewMysqlMacroEngine(tsdbQuery.TimeRange) @@ -102,7 +102,7 @@ func (e *MysqlExecutor) Query(ctx context.Context, dsInfo *models.DataSource, ts } queryResult := &tsdb.QueryResult{Meta: simplejson.New(), RefId: query.RefId} - result.QueryResults[query.RefId] = queryResult + result.Results[query.RefId] = queryResult rawSql, err := macroEngine.Interpolate(rawSql) if err != nil { @@ -138,7 +138,7 @@ func (e *MysqlExecutor) Query(ctx context.Context, dsInfo *models.DataSource, ts } } - return result + return result, nil } func (e MysqlExecutor) TransformToTable(query *tsdb.Query, rows *core.Rows, result *tsdb.QueryResult) error { diff --git a/pkg/tsdb/opentsdb/opentsdb.go b/pkg/tsdb/opentsdb/opentsdb.go index f965392de01..29daa0c3bb4 100644 --- a/pkg/tsdb/opentsdb/opentsdb.go +++ b/pkg/tsdb/opentsdb/opentsdb.go @@ -50,8 +50,8 @@ func init() { tsdb.RegisterTsdbQueryEndpoint("opentsdb", NewOpenTsdbExecutor) } -func (e *OpenTsdbExecutor) Query(ctx context.Context, dsInfo *models.DataSource, queryContext *tsdb.TsdbQuery) *tsdb.BatchResult { - result := &tsdb.BatchResult{} +func (e *OpenTsdbExecutor) Query(ctx context.Context, dsInfo *models.DataSource, queryContext *tsdb.TsdbQuery) (*tsdb.Response, error) { + result := &tsdb.Response{} var tsdbQuery OpenTsdbQuery @@ -69,29 +69,26 @@ func (e *OpenTsdbExecutor) Query(ctx context.Context, dsInfo *models.DataSource, req, err := e.createRequest(dsInfo, tsdbQuery) if err != nil { - result.Error = err - return result + return nil, err } httpClient, err := dsInfo.GetHttpClient() if err != nil { - result.Error = err - return result + return nil, err } res, err := ctxhttp.Do(ctx, httpClient, req) if err != nil { - result.Error = err - return result + return nil, err } queryResult, err := e.parseResponse(tsdbQuery, res) if err != nil { - return result.WithError(err) + return nil, err } - result.QueryResults = queryResult - return result + result.Results = queryResult + return result, nil } func (e *OpenTsdbExecutor) createRequest(dsInfo *models.DataSource, data OpenTsdbQuery) (*http.Request, error) { diff --git a/pkg/tsdb/prometheus/prometheus.go b/pkg/tsdb/prometheus/prometheus.go index 9ca45109835..33219837281 100644 --- a/pkg/tsdb/prometheus/prometheus.go +++ b/pkg/tsdb/prometheus/prometheus.go @@ -80,17 +80,17 @@ func (e *PrometheusExecutor) getClient(dsInfo *models.DataSource) (apiv1.API, er return apiv1.NewAPI(client), nil } -func (e *PrometheusExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) *tsdb.BatchResult { - result := &tsdb.BatchResult{} +func (e *PrometheusExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) { + result := &tsdb.Response{} client, err := e.getClient(dsInfo) if err != nil { - return result.WithError(err) + return nil, err } query, err := parseQuery(tsdbQuery.Queries, tsdbQuery) if err != nil { - return result.WithError(err) + return nil, err } timeRange := apiv1.Range{ @@ -108,15 +108,15 @@ func (e *PrometheusExecutor) Query(ctx context.Context, dsInfo *models.DataSourc value, err := client.QueryRange(ctx, query.Expr, timeRange) if err != nil { - return result.WithError(err) + return nil, err } queryResult, err := parseResponse(value, query) if err != nil { - return result.WithError(err) + return nil, err } - result.QueryResults = queryResult - return result + result.Results = queryResult + return result, nil } func formatLegend(metric model.Metric, query *PrometheusQuery) string { diff --git a/pkg/tsdb/query_endpoint.go b/pkg/tsdb/query_endpoint.go index 74759959b08..4e4f1c9ec9e 100644 --- a/pkg/tsdb/query_endpoint.go +++ b/pkg/tsdb/query_endpoint.go @@ -8,7 +8,7 @@ import ( ) type TsdbQueryEndpoint interface { - Query(ctx context.Context, ds *models.DataSource, query *TsdbQuery) *BatchResult + Query(ctx context.Context, ds *models.DataSource, query *TsdbQuery) (*Response, error) } var registry map[string]GetTsdbQueryEndpointFn diff --git a/pkg/tsdb/request.go b/pkg/tsdb/request.go index 6aa5f0efd38..162116fb2fa 100644 --- a/pkg/tsdb/request.go +++ b/pkg/tsdb/request.go @@ -2,25 +2,17 @@ package tsdb import ( "context" + + "github.com/grafana/grafana/pkg/models" ) -type HandleRequestFunc func(ctx context.Context, req *TsdbQuery) (*Response, error) +type HandleRequestFunc func(ctx context.Context, dsInfo *models.DataSource, req *TsdbQuery) (*Response, error) -func HandleRequest(ctx context.Context, req *TsdbQuery) (*Response, error) { - //TODO niceify - ds := req.Queries[0].DataSource - endpoint, err := getTsdbQueryEndpointFor(ds) +func HandleRequest(ctx context.Context, dsInfo *models.DataSource, req *TsdbQuery) (*Response, error) { + endpoint, err := getTsdbQueryEndpointFor(dsInfo) if err != nil { return nil, err } - res := endpoint.Query(ctx, ds, req) - if res.Error != nil { - return nil, res.Error - } - - return &Response{ - Results: res.QueryResults, - BatchTimings: []*BatchTiming{res.Timings}, - }, nil + return endpoint.Query(ctx, dsInfo, req) } diff --git a/pkg/tsdb/testdata/scenarios.go b/pkg/tsdb/testdata/scenarios.go index 073c220e838..0a7f1467933 100644 --- a/pkg/tsdb/testdata/scenarios.go +++ b/pkg/tsdb/testdata/scenarios.go @@ -29,6 +29,67 @@ func init() { logger.Debug("Initializing TestData Scenario") + registerScenario(&Scenario{ + Id: "exponential_heatmap_bucket_data", + Name: "Exponential heatmap bucket data", + + Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult { + to := context.TimeRange.GetToAsMsEpoch() + + var series []*tsdb.TimeSeries + start := 1 + factor := 2 + for i := 0; i < 10; i++ { + timeWalkerMs := context.TimeRange.GetFromAsMsEpoch() + serie := &tsdb.TimeSeries{Name: strconv.Itoa(start)} + start *= factor + + points := make(tsdb.TimeSeriesPoints, 0) + for j := int64(0); j < 100 && timeWalkerMs < to; j++ { + v := float64(rand.Int63n(100)) + points = append(points, tsdb.NewTimePoint(null.FloatFrom(v), float64(timeWalkerMs))) + timeWalkerMs += query.IntervalMs * 50 + } + + serie.Points = points + series = append(series, serie) + } + + queryRes := tsdb.NewQueryResult() + queryRes.Series = append(queryRes.Series, series...) + return queryRes + }, + }) + + registerScenario(&Scenario{ + Id: "linear_heatmap_bucket_data", + Name: "Linear heatmap bucket data", + + Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult { + to := context.TimeRange.GetToAsMsEpoch() + + var series []*tsdb.TimeSeries + for i := 0; i < 10; i++ { + timeWalkerMs := context.TimeRange.GetFromAsMsEpoch() + serie := &tsdb.TimeSeries{Name: strconv.Itoa(i * 10)} + + points := make(tsdb.TimeSeriesPoints, 0) + for j := int64(0); j < 100 && timeWalkerMs < to; j++ { + v := float64(rand.Int63n(100)) + points = append(points, tsdb.NewTimePoint(null.FloatFrom(v), float64(timeWalkerMs))) + timeWalkerMs += query.IntervalMs * 50 + } + + serie.Points = points + series = append(series, serie) + } + + queryRes := tsdb.NewQueryResult() + queryRes.Series = append(queryRes.Series, series...) + return queryRes + }, + }) + registerScenario(&Scenario{ Id: "random_walk", Name: "Random Walk", diff --git a/pkg/tsdb/testdata/testdata.go b/pkg/tsdb/testdata/testdata.go index 2ed19651130..a1ab250ad37 100644 --- a/pkg/tsdb/testdata/testdata.go +++ b/pkg/tsdb/testdata/testdata.go @@ -24,19 +24,19 @@ func init() { tsdb.RegisterTsdbQueryEndpoint("grafana-testdata-datasource", NewTestDataExecutor) } -func (e *TestDataExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) *tsdb.BatchResult { - result := &tsdb.BatchResult{} - result.QueryResults = make(map[string]*tsdb.QueryResult) +func (e *TestDataExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) { + result := &tsdb.Response{} + result.Results = make(map[string]*tsdb.QueryResult) for _, query := range tsdbQuery.Queries { scenarioId := query.Model.Get("scenarioId").MustString("random_walk") if scenario, exist := ScenarioRegistry[scenarioId]; exist { - result.QueryResults[query.RefId] = scenario.Handler(query, tsdbQuery) - result.QueryResults[query.RefId].RefId = query.RefId + result.Results[query.RefId] = scenario.Handler(query, tsdbQuery) + result.Results[query.RefId].RefId = query.RefId } else { e.log.Error("Scenario not found", "scenarioId", scenarioId) } } - return result + return result, nil } diff --git a/pkg/tsdb/tsdb_test.go b/pkg/tsdb/tsdb_test.go index 5e84a646a31..3ce85cd8c7f 100644 --- a/pkg/tsdb/tsdb_test.go +++ b/pkg/tsdb/tsdb_test.go @@ -19,7 +19,7 @@ func TestMetricQuery(t *testing.T) { fakeExecutor := registerFakeExecutor() fakeExecutor.Return("A", TimeSeriesSlice{&TimeSeries{Name: "argh"}}) - res, err := HandleRequest(context.TODO(), req) + res, err := HandleRequest(context.TODO(), &models.DataSource{Id: 1, Type: "test"}, req) So(err, ShouldBeNil) Convey("Should return query results", func() { @@ -40,18 +40,13 @@ func TestMetricQuery(t *testing.T) { fakeExecutor.Return("A", TimeSeriesSlice{&TimeSeries{Name: "argh"}}) fakeExecutor.Return("B", TimeSeriesSlice{&TimeSeries{Name: "barg"}}) - res, err := HandleRequest(context.TODO(), req) + res, err := HandleRequest(context.TODO(), &models.DataSource{Id: 1, Type: "test"}, req) So(err, ShouldBeNil) Convey("Should return query results", func() { So(len(res.Results), ShouldEqual, 2) So(res.Results["B"].Series[0].Name, ShouldEqual, "barg") }) - - Convey("Should have been batched in one request", func() { - So(len(res.BatchTimings), ShouldEqual, 1) - }) - }) Convey("When query uses data source of unknown type", t, func() { @@ -61,7 +56,7 @@ func TestMetricQuery(t *testing.T) { }, } - _, err := HandleRequest(context.TODO(), req) + _, err := HandleRequest(context.TODO(), &models.DataSource{Id: 12, Type: "testjughjgjg"}, req) So(err, ShouldNotBeNil) }) } diff --git a/public/app/app.ts b/public/app/app.ts index 27039c01350..059c7a6b3f0 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -9,6 +9,8 @@ import 'angular-sanitize'; import 'angular-dragdrop'; import 'angular-bindonce'; import 'angular-ui'; +import 'react'; +import 'react-dom'; import 'ngreact'; import $ from 'jquery'; diff --git a/public/app/core/components/PasswordStrength.tsx b/public/app/core/components/PasswordStrength.tsx index 52e8a44165d..e5e30f5d721 100644 --- a/public/app/core/components/PasswordStrength.tsx +++ b/public/app/core/components/PasswordStrength.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import 'react-dom'; import coreModule from '../core_module'; export interface IProps { diff --git a/public/app/core/core.ts b/public/app/core/core.ts index 56e6dd4dd6f..309201e26e8 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -8,7 +8,6 @@ import "./directives/dropdown_typeahead"; import "./directives/metric_segment"; import "./directives/misc"; import "./directives/ng_model_on_blur"; -import "./directives/password_strength"; import "./directives/spectrum_picker"; import "./directives/tags"; import "./directives/value_select_dropdown"; diff --git a/public/app/core/directives/misc.js b/public/app/core/directives/misc.js index f3667c42c13..c43d1dd614c 100644 --- a/public/app/core/directives/misc.js +++ b/public/app/core/directives/misc.js @@ -25,7 +25,7 @@ function (angular, require, coreModule, kbn) { getText: '&clipboardButton' }, link: function(scope, elem) { - require(['vendor/clipboard/dist/clipboard'], function(Clipboard) { + require(['clipboard'], function(Clipboard) { scope.clipboard = new Clipboard(elem[0], { text: function() { return scope.getText(); diff --git a/public/app/core/directives/password_strength.js b/public/app/core/directives/password_strength.js deleted file mode 100644 index 089ff08c13b..00000000000 --- a/public/app/core/directives/password_strength.js +++ /dev/null @@ -1,45 +0,0 @@ -define([ - '../core_module', -], -function (coreModule) { - 'use strict'; - - coreModule.default.directive('passwordStrength2', function() { - var template = '
' + - '{{strengthText}}' + - '
'; - return { - template: template, - scope: { - password: "=", - }, - link: function($scope) { - - $scope.strengthClass = ''; - - function passwordChanged(newValue) { - if (!newValue) { - $scope.strengthText = ""; - $scope.strengthClass = "hidden"; - return; - } - if (newValue.length < 4) { - $scope.strengthText = "strength: weak sauce."; - $scope.strengthClass = "password-strength-bad"; - return; - } - if (newValue.length <= 8) { - $scope.strengthText = "strength: you can do better."; - $scope.strengthClass = "password-strength-ok"; - return; - } - - $scope.strengthText = "strength: strong like a bull."; - $scope.strengthClass = "password-strength-good"; - } - - $scope.$watch("password", passwordChanged); - } - }; - }); -}); diff --git a/public/app/features/alerting/alert_def.ts b/public/app/features/alerting/alert_def.ts index d80c299ec6d..48cd4b056d9 100644 --- a/public/app/features/alerting/alert_def.ts +++ b/public/app/features/alerting/alert_def.ts @@ -106,6 +106,8 @@ function getStateDisplayModel(state) { }; } } + + throw {message: 'Unknown alert state'}; } function joinEvalMatches(matches, separator: string) { diff --git a/public/app/features/alerting/alert_tab_ctrl.ts b/public/app/features/alerting/alert_tab_ctrl.ts index 57cb13cc993..677eec31060 100644 --- a/public/app/features/alerting/alert_tab_ctrl.ts +++ b/public/app/features/alerting/alert_tab_ctrl.ts @@ -84,7 +84,7 @@ export class AlertTabCtrl { }); } - getNotificationIcon(type) { + getNotificationIcon(type): string { switch (type) { case "email": return "fa fa-envelope"; case "slack": return "fa fa-slack"; @@ -95,6 +95,7 @@ export class AlertTabCtrl { case "hipchat": return "fa fa-mail-forward"; case "pushover": return "fa fa-mobile"; } + return 'fa fa-bell'; } getNotifications() { diff --git a/public/app/features/dashboard/export/export_modal.html b/public/app/features/dashboard/export/export_modal.html index 01e47849439..0598c612fd6 100644 --- a/public/app/features/dashboard/export/export_modal.html +++ b/public/app/features/dashboard/export/export_modal.html @@ -11,7 +11,7 @@
diff --git a/public/app/features/panel/query_troubleshooter.ts b/public/app/features/panel/query_troubleshooter.ts index 94357e53e5d..1fc0b6a3dc0 100644 --- a/public/app/features/panel/query_troubleshooter.ts +++ b/public/app/features/panel/query_troubleshooter.ts @@ -69,10 +69,11 @@ export class QueryTroubleshooterCtrl { } } - getClipboardText() { + getClipboardText(): string { if (this.jsonExplorer) { return JSON.stringify(this.jsonExplorer.json, null, 2); } + return ''; } onRequestResponse(data) { diff --git a/public/app/features/plugins/import_list/import_list.ts b/public/app/features/plugins/import_list/import_list.ts index 08156cb506f..a7dca22cd80 100644 --- a/public/app/features/plugins/import_list/import_list.ts +++ b/public/app/features/plugins/import_list/import_list.ts @@ -38,6 +38,8 @@ export class DashImportListCtrl { }); }, 500); }); + } else { + return Promise.resolve(); } }); } diff --git a/public/app/features/plugins/plugin_edit_ctrl.ts b/public/app/features/plugins/plugin_edit_ctrl.ts index 7c0935d6e91..d2e704803ba 100644 --- a/public/app/features/plugins/plugin_edit_ctrl.ts +++ b/public/app/features/plugins/plugin_edit_ctrl.ts @@ -80,6 +80,7 @@ export class PluginEditCtrl { case 'app': return 'icon-gf icon-gf-apps'; case 'page': return 'icon-gf icon-gf-endpoint-tiny'; case 'dashboard': return 'icon-gf icon-gf-dashboard'; + default: return 'icon-gf icon-gf-apps'; } } diff --git a/public/app/features/templating/editor_ctrl.ts b/public/app/features/templating/editor_ctrl.ts index 44a73b75f72..c10bc5bb5ec 100644 --- a/public/app/features/templating/editor_ctrl.ts +++ b/public/app/features/templating/editor_ctrl.ts @@ -54,7 +54,7 @@ export class VariableEditorCtrl { $scope.isValid = function() { if (!$scope.ctrl.form.$valid) { - return; + return false; } if (!$scope.current.name.match(/^\w+$/)) { diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.ts b/public/app/plugins/datasource/influxdb/query_ctrl.ts index 2240183fa48..5c5bece75a2 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.ts +++ b/public/app/plugins/datasource/influxdb/query_ctrl.ts @@ -352,12 +352,13 @@ export class InfluxQueryCtrl extends QueryCtrl { this.panelCtrl.refresh(); } - getTagValueOperator(tagValue, tagOperator) { + getTagValueOperator(tagValue, tagOperator): string { if (tagOperator !== '=~' && tagOperator !== '!~' && /^\/.*\/$/.test(tagValue)) { return '=~'; } else if ((tagOperator === '=~' || tagOperator === '!~') && /^(?!\/.*\/$)/.test(tagValue)) { return '='; } + return null; } getCollapsedText() { diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index 235198b21ba..e3f5cc474f5 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -99,6 +99,7 @@ export class PrometheusDatasource { var query: any = {}; query.expr = this.templateSrv.replace(target.expr, options.scopedVars, self.interpolateQueryExpr); query.requestId = options.panelId + target.refId; + query.instant = target.instant; var interval = this.templateSrv.replace(target.interval, options.scopedVars) || options.interval; var intervalFactor = target.intervalFactor || 1; @@ -114,7 +115,11 @@ export class PrometheusDatasource { } var allQueryPromise = _.map(queries, query => { - return this.performTimeSeriesQuery(query, start, end); + if (!query.instant) { + return this.performTimeSeriesQuery(query, start, end); + } else { + return this.performInstantQuery(query, end); + } }); return this.$q.all(allQueryPromise).then(responseList => { @@ -129,7 +134,11 @@ export class PrometheusDatasource { result.push(self.transformMetricDataToTable(response.data.data.result)); } else { for (let metricData of response.data.data.result) { - result.push(self.transformMetricData(metricData, activeTargets[index], start, end)); + if (response.data.data.resultType === 'matrix') { + result.push(self.transformMetricData(metricData, activeTargets[index], start, end)); + } else if (response.data.data.resultType === 'vector') { + result.push(self.transformInstantMetricData(metricData, activeTargets[index])); + } } } }); @@ -156,6 +165,11 @@ export class PrometheusDatasource { return this._request('GET', url, query.requestId); } + performInstantQuery(query, time) { + var url = '/api/v1/query?query=' + encodeURIComponent(query.expr) + '&time=' + time; + return this._request('GET', url, query.requestId); + } + performSuggestQuery(query, cache = false) { var url = '/api/v1/label/__name__/values'; @@ -317,6 +331,9 @@ export class PrometheusDatasource { // Populate rows, set value to empty string when label not present. _.each(md, function(series) { + if (series.value) { + series.values = [series.value]; + } if (series.values) { for (i = 0; i < series.values.length; i++) { var values = series.values[i]; @@ -340,6 +357,13 @@ export class PrometheusDatasource { return table; } + transformInstantMetricData(md, options) { + var dps = [], metricLabel = null; + metricLabel = this.createMetricLabel(md.metric, options); + dps.push([parseFloat(md.value[1]), md.value[0] * 1000]); + return { target: metricLabel, datapoints: dps }; + } + createMetricLabel(labelData, options) { if (_.isUndefined(options) || _.isEmpty(options.legendFormat)) { return this.getOriginalMetricName(labelData); diff --git a/public/app/plugins/datasource/prometheus/metric_find_query.js b/public/app/plugins/datasource/prometheus/metric_find_query.js index a33044fddf6..6bfd324cd6e 100644 --- a/public/app/plugins/datasource/prometheus/metric_find_query.js +++ b/public/app/plugins/datasource/prometheus/metric_find_query.js @@ -95,9 +95,7 @@ function (_) { PrometheusMetricFindQuery.prototype.queryResultQuery = function(query) { var end = this.datasource.getPrometheusTime(this.range.to, true); - var url = '/api/v1/query?query=' + encodeURIComponent(query) + '&time=' + end; - - return this.datasource._request('GET', url) + return this.datasource.performInstantQuery({ expr: query }, end) .then(function(result) { return _.map(result.data.data.result, function(metricData) { var text = metricData.metric.__name__ || ''; diff --git a/public/app/plugins/datasource/prometheus/partials/query.editor.html b/public/app/plugins/datasource/prometheus/partials/query.editor.html index 9b06d4116f4..98fd01eee15 100644 --- a/public/app/plugins/datasource/prometheus/partials/query.editor.html +++ b/public/app/plugins/datasource/prometheus/partials/query.editor.html @@ -45,6 +45,8 @@
+ +