Compare commits
127 Commits
v7.3.0-bet
...
v7.3.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f25d63954b | ||
|
|
11f305f88a | ||
|
|
3795ad5af0 | ||
|
|
f5d8063da2 | ||
|
|
751f32c752 | ||
|
|
8e4fb74e8f | ||
|
|
13c8669ac7 | ||
|
|
e9553ddd72 | ||
|
|
7922b400ef | ||
|
|
3ed851151f | ||
|
|
76d3a7a1e6 | ||
|
|
2ec763a0d4 | ||
|
|
240417f82f | ||
|
|
d95ac6c85c | ||
|
|
1e6c14a750 | ||
|
|
f511cf6727 | ||
|
|
9b70e04cdf | ||
|
|
95a9fad671 | ||
|
|
e6327b0536 | ||
|
|
df367b0634 | ||
|
|
2069630cb3 | ||
|
|
a578cdf18c | ||
|
|
81e18c85bf | ||
|
|
d7208411cc | ||
|
|
14c494085e | ||
|
|
3a50fc8db0 | ||
|
|
7472e37cf9 | ||
|
|
be6425d461 | ||
|
|
f34fecbca0 | ||
|
|
509174df31 | ||
|
|
6ada37b445 | ||
|
|
6d79790397 | ||
|
|
089c636acf | ||
|
|
fcd66eef29 | ||
|
|
cffb1cd98d | ||
|
|
881a595651 | ||
|
|
004104717c | ||
|
|
b345e28a2d | ||
|
|
fa1db4dc12 | ||
|
|
9a3fbb8782 | ||
|
|
7292e1508e | ||
|
|
2489dc4d3a | ||
|
|
ca8de25f0c | ||
|
|
ff7c462600 | ||
|
|
41bbafe979 | ||
|
|
37e4a19ea8 | ||
|
|
f04131c9c6 | ||
|
|
7e6c34fc20 | ||
|
|
0e5da44bc1 | ||
|
|
fc44872ca2 | ||
|
|
c88fc42fc6 | ||
|
|
d05c3462da | ||
|
|
8db22cef96 | ||
|
|
edfe914cb0 | ||
|
|
4cf6c916c7 | ||
|
|
697e4f7037 | ||
|
|
ca4fdc46fd | ||
|
|
e086a96161 | ||
|
|
945573eb93 | ||
|
|
b8ad4eaab3 | ||
|
|
f034cbef50 | ||
|
|
23be6e3898 | ||
|
|
7798f01cc9 | ||
|
|
38b96278c8 | ||
|
|
f142752ad6 | ||
|
|
1cfe644d51 | ||
|
|
d8ac457ebc | ||
|
|
3d4ff87721 | ||
|
|
0c5524786b | ||
|
|
c0adf1022b | ||
|
|
706cc59d1d | ||
|
|
f9f3b9d953 | ||
|
|
1b14c6b8db | ||
|
|
d8674013cb | ||
|
|
fc86862533 | ||
|
|
3636749a43 | ||
|
|
f30f6d8a35 | ||
|
|
202b9e5ca9 | ||
|
|
c9a6e09235 | ||
|
|
c03fbac5db | ||
|
|
de19ba97cb | ||
|
|
e5be54b0f0 | ||
|
|
70bf01f4f9 | ||
|
|
0ee8427386 | ||
|
|
7995ab999f | ||
|
|
b51303b83d | ||
|
|
c14ee2d245 | ||
|
|
d394be2555 | ||
|
|
f523500df2 | ||
|
|
d55ca82f0b | ||
|
|
7f9990c889 | ||
|
|
6a2d9224f1 | ||
|
|
88ae4c0e64 | ||
|
|
f11bfe95da | ||
|
|
49dd35f8d9 | ||
|
|
557639a458 | ||
|
|
6668161a88 | ||
|
|
f81257c2f2 | ||
|
|
e8d399ef94 | ||
|
|
03563abba0 | ||
|
|
43928c4c88 | ||
|
|
3069d764f8 | ||
|
|
de9ac280b2 | ||
|
|
4ff80d541b | ||
|
|
4f4fcf6331 | ||
|
|
bf8837f6f8 | ||
|
|
95a3c6a024 | ||
|
|
1ff5718af9 | ||
|
|
e123e50d27 | ||
|
|
44f691ff91 | ||
|
|
12a69876c7 | ||
|
|
5bb203df99 | ||
|
|
23ae63913f | ||
|
|
9b3981fb3c | ||
|
|
c933d58893 | ||
|
|
1c61f3fd45 | ||
|
|
be4b32270a | ||
|
|
1e027f5289 | ||
|
|
99613f86f8 | ||
|
|
43a1cb25b9 | ||
|
|
648a149813 | ||
|
|
d8ed14f5a5 | ||
|
|
8f5c9b192f | ||
|
|
0a184cb84f | ||
|
|
be4ef1dcd8 | ||
|
|
de86b51fb5 | ||
|
|
25653e15a6 |
1155
.circleci/config.yml
1155
.circleci/config.yml
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,9 @@
|
||||
load('scripts/pr.star', 'pr_pipelines')
|
||||
load('scripts/master.star', 'master_pipelines')
|
||||
load('scripts/release.star', 'release_pipelines', 'test_release_pipelines')
|
||||
load('scripts/version.star', 'version_branch_pipelines')
|
||||
|
||||
def main(ctx):
|
||||
edition = 'oss'
|
||||
return pr_pipelines(edition=edition) + master_pipelines(edition=edition) + release_pipelines() + \
|
||||
test_release_pipelines()
|
||||
test_release_pipelines() + version_branch_pipelines()
|
||||
|
||||
1217
.drone.yml
1217
.drone.yml
File diff suppressed because it is too large
Load Diff
27
.github/workflows/bump-version.yml
vendored
Normal file
27
.github/workflows/bump-version.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Bump version
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
required: true
|
||||
default: '7.x.x'
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Actions
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: "grafana/grafana-github-actions"
|
||||
path: ./actions
|
||||
ref: main
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '12'
|
||||
- name: Install Actions
|
||||
run: npm install --production --prefix ./actions
|
||||
- name: Run bump version
|
||||
uses: ./actions/bump-version
|
||||
with:
|
||||
token: ${{secrets.GH_BOT_ACCESS_TOKEN}}
|
||||
metricsWriteAPIKey: ${{secrets.GRAFANA_MISC_STATS_API_KEY}}
|
||||
13
build.go
13
build.go
@@ -37,6 +37,7 @@ var (
|
||||
libc string
|
||||
pkgArch string
|
||||
version string = "v1"
|
||||
buildTags []string
|
||||
// deb & rpm does not support semver so have to handle their version a little differently
|
||||
linuxPackageVersion string = "v1"
|
||||
linuxPackageIteration string = ""
|
||||
@@ -59,11 +60,13 @@ func main() {
|
||||
log.SetFlags(0)
|
||||
|
||||
var buildIdRaw string
|
||||
var buildTagsRaw string
|
||||
|
||||
flag.StringVar(&goarch, "goarch", runtime.GOARCH, "GOARCH")
|
||||
flag.StringVar(&goos, "goos", runtime.GOOS, "GOOS")
|
||||
flag.StringVar(&gocc, "cc", "", "CC")
|
||||
flag.StringVar(&libc, "libc", "", "LIBC")
|
||||
flag.StringVar(&buildTagsRaw, "build-tags", "", "Sets custom build tags")
|
||||
flag.BoolVar(&cgo, "cgo-enabled", cgo, "Enable cgo")
|
||||
flag.StringVar(&pkgArch, "pkg-arch", "", "PKG ARCH")
|
||||
flag.BoolVar(&race, "race", race, "Use race detector")
|
||||
@@ -89,6 +92,10 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
if len(buildTagsRaw) > 0 {
|
||||
buildTags = strings.Split(buildTagsRaw, ",")
|
||||
}
|
||||
|
||||
log.Printf("Version: %s, Linux Version: %s, Package Iteration: %s\n", version, linuxPackageVersion, linuxPackageIteration)
|
||||
|
||||
if flag.NArg() == 0 {
|
||||
@@ -105,16 +112,16 @@ func main() {
|
||||
|
||||
case "build-srv", "build-server":
|
||||
clean()
|
||||
doBuild("grafana-server", "./pkg/cmd/grafana-server", []string{})
|
||||
doBuild("grafana-server", "./pkg/cmd/grafana-server", buildTags)
|
||||
|
||||
case "build-cli":
|
||||
clean()
|
||||
doBuild("grafana-cli", "./pkg/cmd/grafana-cli", []string{})
|
||||
doBuild("grafana-cli", "./pkg/cmd/grafana-cli", buildTags)
|
||||
|
||||
case "build":
|
||||
//clean()
|
||||
for _, binary := range binaries {
|
||||
doBuild(binary, "./pkg/cmd/"+binary, []string{})
|
||||
doBuild(binary, "./pkg/cmd/"+binary, buildTags)
|
||||
}
|
||||
|
||||
case "build-frontend":
|
||||
|
||||
@@ -670,7 +670,7 @@ disable_total_stats = false
|
||||
basic_auth_username =
|
||||
basic_auth_password =
|
||||
|
||||
# Metrics environment info adds dimensions to the `grafana_environment_info` metric, which
|
||||
# Metrics environment info adds dimensions to the `grafana_environment_info` metric, which
|
||||
# can expose more information about the Grafana instance.
|
||||
[metrics.environment_info]
|
||||
#exampleLabel1 = exampleValue1
|
||||
@@ -705,6 +705,8 @@ sampler_type = const
|
||||
# and indicates the initial sampling rate before the actual one
|
||||
# is received from the mothership
|
||||
sampler_param = 1
|
||||
# sampling_server_url is the URL of a sampling manager providing a sampling strategy.
|
||||
sampling_server_url =
|
||||
# Whether or not to use Zipkin span propagation (x-b3- HTTP headers).
|
||||
zipkin_propagation = false
|
||||
# Setting this to true disables shared RPC spans.
|
||||
@@ -768,6 +770,7 @@ enable_alpha = false
|
||||
app_tls_skip_verify_insecure = false
|
||||
# Enter a comma-separated list of plugin identifiers to identify plugins that are allowed to be loaded even if they lack a valid signature.
|
||||
allow_loading_unsigned_plugins =
|
||||
marketplace_url = https://grafana.com/grafana/plugins/
|
||||
|
||||
#################################### Grafana Image Renderer Plugin ##########################
|
||||
[plugin.grafana-image-renderer]
|
||||
|
||||
@@ -664,7 +664,7 @@
|
||||
; basic_auth_username =
|
||||
; basic_auth_password =
|
||||
|
||||
# Metrics environment info adds dimensions to the `grafana_environment_info` metric, which
|
||||
# Metrics environment info adds dimensions to the `grafana_environment_info` metric, which
|
||||
# can expose more information about the Grafana instance.
|
||||
[metrics.environment_info]
|
||||
#exampleLabel1 = exampleValue1
|
||||
@@ -697,6 +697,8 @@
|
||||
# and indicates the initial sampling rate before the actual one
|
||||
# is received from the mothership
|
||||
;sampler_param = 1
|
||||
# sampling_server_url is the URL of a sampling manager providing a sampling strategy.
|
||||
;sampling_server_url =
|
||||
# Whether or not to use Zipkin propagation (x-b3- HTTP headers).
|
||||
;zipkin_propagation = false
|
||||
# Setting this to true disables shared RPC spans.
|
||||
@@ -756,6 +758,7 @@
|
||||
;app_tls_skip_verify_insecure = false
|
||||
# Enter a comma-separated list of plugin identifiers to identify plugins that are allowed to be loaded even if they lack a valid signature.
|
||||
;allow_loading_unsigned_plugins =
|
||||
;marketplace_url = https://grafana.com/grafana/plugins/
|
||||
|
||||
#################################### Grafana Image Renderer Plugin ##########################
|
||||
[plugin.grafana-image-renderer]
|
||||
|
||||
@@ -1159,6 +1159,10 @@ This is the sampler configuration parameter. Depending on the value of `sampler_
|
||||
|
||||
May be set with the environment variable `JAEGER_SAMPLER_PARAM`.
|
||||
|
||||
### sampling_server_url
|
||||
|
||||
sampling_server_url is the URL of a sampling manager providing a sampling strategy.
|
||||
|
||||
### zipkin_propagation
|
||||
|
||||
Default value is `false`.
|
||||
@@ -1336,6 +1340,10 @@ Set to `true` if you want to test alpha plugins that are not yet ready for gener
|
||||
|
||||
Enter a comma-separated list of plugin identifiers to identify plugins that are allowed to be loaded even if they lack a valid signature.
|
||||
|
||||
### marketplace_url
|
||||
|
||||
Custom install/learn more url for enterprise plugins. Defaults to https://grafana.com/grafana/plugins/.
|
||||
|
||||
<hr>
|
||||
|
||||
## [plugin.grafana-image-renderer]
|
||||
|
||||
@@ -248,3 +248,19 @@ datasources:
|
||||
logMessageField: message
|
||||
logLevelField: fields.level
|
||||
```
|
||||
|
||||
## Amazon Elasticsearch Service
|
||||
|
||||
AWS users using Amazon's Elasticsearch Service can use Grafana's Elasticsearch data source to visualize Elasticsearch data.
|
||||
If you are using an AWS Identity and Access Management (IAM) policy to control access to your Amazon Elasticsearch Service domain, then you must use AWS Signature Version 4 (AWS SigV4) to sign all requests to that domain.
|
||||
For more details on AWS SigV4, refer to the [AWS documentation](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html).
|
||||
|
||||
### AWS Signature Version 4 authentication
|
||||
|
||||
> **Note:** Only available in Grafana v7.3+.
|
||||
|
||||
In order to sign requests to your Amazon Elasticsearch Service domain, SigV4 can be enabled in the Grafana [configuration]({{< relref "../administration/configuration.md#sigv4_auth_enabled" >}}).
|
||||
|
||||
Once AWS SigV4 is enabled, it can be configured on the Elasticsearch data source configuration page. Refer to [Cloudwatch authentication]({{<relref "./cloudwatch.md#authentication" >}}) for more information about authentication options.
|
||||
|
||||
{{< docs-imagebox img="/img/docs/v73/elasticsearch-sigv4-config-editor.png" max-width="500px" class="docs-image--no-shadow" caption="SigV4 configuration for AWS Elasticsearch Service" >}}
|
||||
|
||||
@@ -53,7 +53,7 @@ You can close the newly created query by clicking on the Close Split button.
|
||||
|
||||
> Share shortened link is only available in Grafana 7.3 and above.
|
||||
|
||||
The Share shortened link capability allows you to create smaller and simpler URLs of the format /goto/:uid instead of using longer URLs containing complex query parameters. You can create a shortened link by clicking on the **Share** option in Explore toolbar.
|
||||
The Share shortened link capability allows you to create smaller and simpler URLs of the format /goto/:uid instead of using longer URLs containing complex query parameters. You can create a shortened link by clicking on the **Share** option in Explore toolbar. Please note that any shortened links that are never used will be automatically deleted after 7 days.
|
||||
|
||||
## Query history
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ Overrides allow you to change the settings for one or more fields. Field options
|
||||
|
||||
For example, you could change the number of decimal places shown in all numeric fields or columns by changing the **Decimals** option for **Fields with type** that matches **Numeric**. For more information about options, refer to:
|
||||
- [Standard field options]({{< relref "standard-field-options.md" >}}), which apply to all panel visualizations that allow transformations.
|
||||
- [Table field options]({{< relref "table-field-options.md" >}}), which only apply to table panel visualizations.
|
||||
- There can also be visualization specific field display options
|
||||
|
||||
## Add a field override
|
||||
|
||||
@@ -26,8 +26,6 @@ You can override as many field options as you want to.
|
||||
- **Fields with type -** Allows you to select fields by type, such as string, numeric, and so on. Properties you add to a rule with this selector are applied to all fields that match the selected type.
|
||||
1. Click **Add override property**.
|
||||
1. Select the field option that you want to apply.
|
||||
- [Standard field options]({{< relref "standard-field-options.md" >}}), which apply to all panel visualizations that allow transformations.
|
||||
- [Table field options]({{< relref "table-field-options.md" >}}), which only apply to table panel visualizations.
|
||||
1. Enter options by adding values in the fields. To return options to default values, delete the white text in the fields.
|
||||
1. Continue to add overrides to this field by clicking **Add override property**, or you can click **Add override** and select a different field to add overrides to.
|
||||
1. When finished, click **Save** to save all panel edits to the dashboard.
|
||||
|
||||
@@ -37,11 +37,11 @@ Can do everything scoped to the organization. For example:
|
||||
- Can view, add, and edit dashboards, panels, and alert rules in dashboards they have access to. This can be disabled on specific folders and dashboards.
|
||||
- Can create, update, or delete playlists.
|
||||
- Can access Explore.
|
||||
- Can add, edit, or delete alert notification channels.
|
||||
- Cannot add, edit, or delete data sources.
|
||||
- Cannot add, edit, or delete alert notification channels.
|
||||
- Cannot manage other organizations, users, and teams.
|
||||
|
||||
This role can be changed with the Grafana server setting [editors_can_admin]({{< relref "../administration/configuration.md#editors_can_admin" >}}). If you set this to `true`, then users with the Editor role can also administrate dashboards, folders and teams they create. This is especially useful for enabling self-organizing teams to administer their own dashboards.
|
||||
This role can be changed with the Grafana server setting [editors_can_admin]({{< relref "../administration/configuration.md#editors_can_admin" >}}). If you set this to `true`, then users with the Editor role can also administrate dashboards, folders, and teams they create. This is especially useful for enabling self-organizing teams to administer their own dashboards.
|
||||
|
||||
## Viewer role
|
||||
|
||||
|
||||
@@ -15,16 +15,53 @@ weight = -17
|
||||
|
||||
This topic includes the release notes for the Grafana v7.3. For all details, read the full [CHANGELOG.md](https://github.com/grafana/grafana/blob/master/CHANGELOG.md).
|
||||
|
||||
## Highlights
|
||||
|
||||
Grafana 7.3 comes with a number of features and enhancements:
|
||||
The main highlights are:
|
||||
|
||||
- [**Google Cloud Monitoring:** Out of the box dashboards]({{< relref "#cloud-monitoring-out-of-the-box-dashboards" >}})
|
||||
- [**Shorten URL for dashboards and Explore**]({{< relref "#shorten-url-for-dashboards-and-explore" >}})
|
||||
- [**Table improvements and new image cell mode**]({{< relref "#table-improvements-and-new-image-cell-mode" >}})
|
||||
- [**New color scheme option**]({{< relref "#new-color-scheme-option" >}})
|
||||
- [**SigV4 Authentication for Amazon Elasticsearch Service**]({{< relref "#sigv4-authentication-for-aws-users" >}})
|
||||
|
||||
#### Cloud monitoring out-of-the-box dashboards
|
||||
## Table improvements and new image cell mode
|
||||
|
||||
The updated Cloud monitoring data source is shipped with pre-configured dashboards for five of the most popular GCP services:
|
||||
The table has been updated with improved hover behavior for cells that have longer content than what fits the current column width. As you can see
|
||||
in the animated gif below the cell will automatically expand to show you full content of the cell.
|
||||
|
||||
{{< figure src="/img/docs/v73/table_hover.gif" max-width="900px" caption="Table hover" >}}
|
||||
|
||||
Another new feature that can be seen in the image above is the new image cell display mode. If you have a field value that is an image URL or a base64 encoded image you can configure the table to display it as an image.
|
||||
|
||||
## New color scheme option
|
||||
|
||||
{{< figure src="/img/docs/v73/color_scheme_dropdown.png" max-width="450px" caption="Color scheme" class="pull-right" >}}
|
||||
|
||||
A new standard field [color scheme]({{< relref "../panels/field-options/standard-field-options.md#color-scheme" >}}) option has been added. This new option will provide a unified way for all new panels to specify how colors should be assigned.
|
||||
|
||||
* **Single color**: Specify a single color, useful in an override rule.
|
||||
* **From thresholds**: Informs Grafana to take the color from the matching threshold.
|
||||
* **Classic palette**: Grafana will assign color by looking up a color in a palette by series index. Useful for Graphs and pie charts and other categorical data visualizations.
|
||||
* **Green-Yellow-Red (by value)**: This is a continuous color scheme where Grafana will interpolate a color based on the value being displayed and the field min & max values.
|
||||
* **Blue-Yellow-Red (by value)**: Same as above but different colors.
|
||||
* **Blues (by value)**: Same as above but color scheme go from panel background to blue.
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
As you can see this adds new continuous color schemes where Grafana will interpolate colors. A great use of these new color schemes is the table panel where you can color the background and get a heatmap like effect.
|
||||
|
||||
{{< figure src="/img/docs/v73/table_color_scheme.png" max-width="900px" caption="table color scheme" >}}
|
||||
|
||||
Another thing to highlight is that all these new color schemes are theme aware and adapt to the current theme. For example here is how the new monochrome color scheme look like in the light theme:
|
||||
|
||||
{{< figure src="/img/docs/v73/table_color_scheme_mono_light.png" max-width="900px" caption="table color monochrome scheme" >}}
|
||||
|
||||
As this new option is a standard field option it works in every panel. Here is another example from the [Bar Gauge]({{< relref "../panels/visualizations/bar-gauge-panel.md" >}}) panel.
|
||||
|
||||
{{< figure src="/img/docs/v73/bar_gauge_gradient_color_scheme.png" max-width="900px" caption="bar gauge color scheme" >}}
|
||||
|
||||
## Google Cloud monitoring out-of-the-box dashboards
|
||||
|
||||
The updated Google Cloud monitoring data source is shipped with pre-configured dashboards for five of the most popular Google Cloud Platform (GCP) services:
|
||||
|
||||
- BigQuery
|
||||
- Cloud Load Balancing
|
||||
@@ -38,7 +75,13 @@ For more details, see the [Google Cloud Monitoring docs]({{<relref "../datasourc
|
||||
|
||||
## Shorten URL for dashboards and Explore
|
||||
|
||||
This is an amazing new feature that was created in cooperation with one of our community members. The new **share shortened link** capability allows you to create smaller and simpler URLs of the format `/goto/:uid` instead of using longer URLs that can contain complex query parameters. In Explore, you can create a shortened link by clicking on the share button in Explore toolbar. In the dashboards, a shortened url option is available through the share panel or dashboard button.
|
||||
This is an amazing new feature that was created in cooperation with one of our community members. The new share shortened link capability allows you to create smaller and simpler URLs of the format `/goto/:uid` instead of using longer URLs that can contain complex query parameters. In Explore, you can create a shortened link by clicking on the share button in Explore toolbar. In the dashboards, a shortened url option is available through the share panel or dashboard button.
|
||||
|
||||
## SigV4 authentication for AWS users
|
||||
|
||||
You can now configure your Elasticsearch data source to access your Amazon Elasticsearch Service domain directly from Grafana.
|
||||
|
||||
For more details, refer to the [Elasticsearch docs]({{<relref "../datasources/elasticsearch/#aws-signature-version-4-authentication">}}).
|
||||
|
||||
## Grafana Enterprise features
|
||||
|
||||
|
||||
@@ -59,8 +59,7 @@ e2e.scenario({
|
||||
e2e.components.DashboardLinks.dropDown()
|
||||
.should('be.visible')
|
||||
.click()
|
||||
.wait('@tagsTemplatingSearch')
|
||||
.wait('@tagsDemoSearch');
|
||||
.wait('@tagsTemplatingSearch');
|
||||
|
||||
// verify all links, should have p2 value
|
||||
verifyLinks('p2');
|
||||
|
||||
41
go.mod
41
go.mod
@@ -11,8 +11,7 @@ replace github.com/denisenkom/go-mssqldb => github.com/denisenkom/go-mssqldb v0.
|
||||
replace k8s.io/client-go => k8s.io/client-go v0.18.8
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.60.0 // indirect
|
||||
cloud.google.com/go/storage v1.8.0
|
||||
cloud.google.com/go/storage v1.10.0
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f
|
||||
github.com/aws/aws-sdk-go v1.33.12
|
||||
@@ -20,9 +19,8 @@ require (
|
||||
github.com/benbjohnson/clock v0.0.0-20161215174838-7dc76406b6d3
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
|
||||
github.com/centrifugal/centrifuge v0.11.0
|
||||
github.com/crewjam/saml v0.4.1
|
||||
github.com/crewjam/saml v0.4.4-0.20201214083806-0dd2422c212e
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/deepmap/oapi-codegen v1.3.11 // indirect
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec
|
||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
|
||||
github.com/facebookgo/inject v0.0.0-20180706035515-f23751cae28b
|
||||
@@ -37,20 +35,20 @@ require (
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/go-stack/stack v1.8.0
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/golang/protobuf v1.4.2
|
||||
github.com/google/go-cmp v0.5.0
|
||||
github.com/golang/protobuf v1.4.3
|
||||
github.com/google/go-cmp v0.5.2
|
||||
github.com/gosimple/slug v1.4.2
|
||||
github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.78.0
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.79.0
|
||||
github.com/grafana/loki v1.6.0
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.2.1
|
||||
github.com/hashicorp/go-hclog v0.12.2
|
||||
github.com/hashicorp/go-plugin v1.2.2
|
||||
github.com/hashicorp/go-version v1.2.0
|
||||
github.com/inconshreveable/log15 v0.0.0-20180818164646-67afb5ed74ec
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.0.1
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.2.0
|
||||
github.com/jmespath/go-jmespath v0.3.0
|
||||
github.com/jonboulle/clockwork v0.2.1 // indirect
|
||||
github.com/jonboulle/clockwork v0.2.2 // indirect
|
||||
github.com/jung-kurt/gofpdf v1.10.1
|
||||
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect
|
||||
github.com/lib/pq v1.3.0
|
||||
@@ -62,13 +60,13 @@ require (
|
||||
github.com/opentracing/opentracing-go v1.2.0
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.7.1
|
||||
github.com/prometheus/client_golang v1.8.0
|
||||
github.com/prometheus/client_model v0.2.0
|
||||
github.com/prometheus/common v0.10.0
|
||||
github.com/prometheus/common v0.14.0
|
||||
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be // indirect
|
||||
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967
|
||||
github.com/robfig/cron/v3 v3.0.0
|
||||
github.com/russellhaering/goxmldsig v0.0.0-20200902171629-2e1fbc2c5593
|
||||
github.com/russellhaering/goxmldsig v1.1.0
|
||||
github.com/smartystreets/goconvey v1.6.4
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf
|
||||
@@ -77,17 +75,22 @@ require (
|
||||
github.com/uber/jaeger-client-go v2.25.0+incompatible
|
||||
github.com/unknwon/com v1.0.1
|
||||
github.com/urfave/cli/v2 v2.1.1
|
||||
github.com/weaveworks/common v0.0.0-20201119133501-0619918236ec
|
||||
github.com/xorcare/pointer v1.1.0
|
||||
github.com/yudai/gojsondiff v1.0.0
|
||||
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
|
||||
github.com/yudai/pp v2.0.1+incompatible // indirect
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
|
||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
|
||||
golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d // indirect
|
||||
golang.org/x/text v0.3.3 // indirect
|
||||
google.golang.org/grpc v1.30.0
|
||||
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9
|
||||
golang.org/x/net v0.0.0-20201022231255-08b38378de70
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
|
||||
golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd // indirect
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e
|
||||
golang.org/x/tools v0.0.0-20201023150057-2f4fa188d925 // indirect
|
||||
google.golang.org/api v0.33.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5 // indirect
|
||||
google.golang.org/grpc v1.33.1
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
|
||||
gopkg.in/ini.v1 v1.51.0
|
||||
|
||||
126
go.sum
126
go.sum
@@ -15,13 +15,15 @@ cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.60.0 h1:R+tDlceO7Ss+zyvtsdhTxacDyZ1k99xwskQ4FT7ruoM=
|
||||
cloud.google.com/go v0.60.0/go.mod h1:yw2G51M9IfRboUH61Us8GqCeF1PzPblB823Mn2q2eAU=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/bigtable v1.1.0/go.mod h1:B6ByKcIdYmhoyDzmOnQxyOhN6r05qnewYIxxG6L0/b4=
|
||||
cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
@@ -36,6 +38,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0 h1:86K1Gel7BQ9/WmNWn7dTKMvTLFzwtBe5FNqYbi9X35g=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE=
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.6.0/go.mod h1:zmKjrJcdo0aYcVS7bmEeSEBLPA9YJp5bjrofdU3pIXs=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
@@ -226,6 +230,8 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do
|
||||
github.com/crewjam/httperr v0.0.0-20190612203328-a946449404da/go.mod h1:+rmNIXRvYMqLQeR4DHyTvs6y0MEMymTz4vyFpFkKTPs=
|
||||
github.com/crewjam/saml v0.4.1 h1:ZNSRJvdbypQDY2uApMngeIHNcxS6UCRAgiw3S+pmgRU=
|
||||
github.com/crewjam/saml v0.4.1/go.mod h1:vHcshzXm2WkPOV1dcToZa99cCB1h3nPiKLtLYK+erBE=
|
||||
github.com/crewjam/saml v0.4.4-0.20201214083806-0dd2422c212e h1:CFIpybPh+vrxRD6R3t2BCV9hdtlOQudsj1vB1ECXOo4=
|
||||
github.com/crewjam/saml v0.4.4-0.20201214083806-0dd2422c212e/go.mod h1:qCJQpUtZte9R1ZjUBcW8qtCNlinbO363ooNl02S68bk=
|
||||
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
|
||||
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
|
||||
github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8=
|
||||
@@ -244,9 +250,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
|
||||
github.com/deepmap/oapi-codegen v1.3.6/go.mod h1:aBozjEveG+33xPiP55Iw/XbVkhtZHEGLq3nxlX0+hfU=
|
||||
github.com/deepmap/oapi-codegen v1.3.11 h1:Nd3tDQfqgquLmCzyRONHzs5SJEwPPoQcFZxT8MKt1Hs=
|
||||
github.com/deepmap/oapi-codegen v1.3.11/go.mod h1:suMvK7+rKlx3+tpa8ByptmvoXbAV70wERKTOGH3hLp0=
|
||||
github.com/deepmap/oapi-codegen v1.3.13 h1:9HKGCsdJqE4dnrQ8VerFS0/1ZOJPmAhN+g8xgp8y3K4=
|
||||
github.com/deepmap/oapi-codegen v1.3.13/go.mod h1:WAmG5dWY8/PYHt4vKxlt90NsbHMAOCiteYKZMiIRfOo=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec h1:NfhRXXFDPxcF5Cwo06DzeIaE7uuJtAUhsDwH3LNsjos=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
@@ -308,6 +313,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
|
||||
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/structtag v1.1.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
||||
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fluent/fluent-bit-go v0.0.0-20190925192703-ea13c021720c/go.mod h1:WQX+afhrekY9rGK+WT4xvKSlzmia9gDoLYu4GGYGASQ=
|
||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
@@ -319,7 +326,6 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
||||
github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk=
|
||||
github.com/gchaincl/sqlhooks v1.3.0 h1:yKPXxW9a5CjXaVf2HkQn6wn7TZARvbAOAelr3H8vK2Y=
|
||||
github.com/gchaincl/sqlhooks v1.3.0/go.mod h1:9BypXnereMT0+Ys8WGWHqzgkkOfHIhyeUCqXC24ra34=
|
||||
github.com/getkin/kin-openapi v0.2.0/go.mod h1:V1z9xl9oF5Wt7v32ne4FmiF1alpS4dM6mNzoywPOXlk=
|
||||
github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw=
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
@@ -461,6 +467,7 @@ github.com/gocql/gocql v0.0.0-20200121121104-95d072f1b5bb/go.mod h1:DL0ekTmBSTdl
|
||||
github.com/gocql/gocql v0.0.0-20200526081602-cd04bd7f22a7/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY=
|
||||
github.com/godbus/dbus v0.0.0-20190402143921-271e53dc4968/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gogo/googleapis v1.1.0 h1:kFkMAZBNAn4j7K0GiZr8cRYzejq68VbheufiV3YuyFI=
|
||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
@@ -471,6 +478,7 @@ github.com/gogo/protobuf v1.2.2-0.20190730201129-28a6bbf47e48/go.mod h1:SlYgWuQ5
|
||||
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/status v1.0.3 h1:WkVBY59mw7qUNTr/bLwO7J2vesJ0rQ2C3tMXrTd3w5M=
|
||||
github.com/gogo/status v1.0.3/go.mod h1:SavQ51ycCLnc7dGyJxp8YAmudx8xqiVrRf+6IXRsugc=
|
||||
github.com/golang-migrate/migrate/v4 v4.7.0/go.mod h1:Qvut3N4xKWjoH3sokBccML6WyHSnggXm/DvMMnTsQIc=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||
@@ -490,8 +498,8 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@@ -505,8 +513,9 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
@@ -524,8 +533,12 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||
@@ -533,6 +546,8 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190723021845-34ac40c74b70/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
@@ -547,6 +562,8 @@ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
||||
github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go v2.0.2+incompatible h1:silFMLAnr330+NRuag/VjIGF7TLp/LBrV2CJKFLWEww=
|
||||
github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
@@ -571,6 +588,7 @@ github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de/go.mod h1:wJfORR
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
@@ -580,8 +598,8 @@ github.com/gosimple/slug v1.4.2 h1:jDmprx3q/9Lfk4FkGZtvzDQ9Cj9eAmsjzeQGp24PeiQ=
|
||||
github.com/gosimple/slug v1.4.2/go.mod h1:ER78kgg1Mv0NQGlXiDe57DpCyfbNywXXZ9mIorhxAf0=
|
||||
github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4 h1:SPdxCL9BChFTlyi0Khv64vdCW4TMna8+sxL7+Chx+Ag=
|
||||
github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4/go.mod h1:nc0XxBzjeGcrMltCDw269LoWF9S8ibhgxolCdA1R8To=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.78.0 h1:w43X+b36goTvis4TAW5PzhkGuSJokgm3KKYPOYiENAc=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.78.0/go.mod h1:NvxLzGkVhnoBKwzkst6CFfpMFKwAdIUZ1q8ssuLeF60=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.79.0 h1:7NVEIMlF8G9H7XUdLX9jH/g01FllE1GEBcFvzXZD+Kw=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.79.0/go.mod h1:NvxLzGkVhnoBKwzkst6CFfpMFKwAdIUZ1q8ssuLeF60=
|
||||
github.com/grafana/loki v1.6.0 h1:vHuFgfhW1iRMCm7/LgnZi02Ifvqj389vWhyfNJlHQTQ=
|
||||
github.com/grafana/loki v1.6.0/go.mod h1:X+GvtCzAf2ok/xRLLvGB8kuWP1R+75nXnvjCEnenP0s=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
@@ -672,8 +690,8 @@ github.com/influxdata/go-syslog/v3 v3.0.1-0.20200510134747-836dce2cf6da/go.mod h
|
||||
github.com/influxdata/influxdb v1.7.7/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY=
|
||||
github.com/influxdata/influxdb v1.8.0/go.mod h1:SIzcnsjaHRFpmlxpJ4S3NT64qtEKYweNTUMb/vh0OMQ=
|
||||
github.com/influxdata/influxdb v1.8.1/go.mod h1:SIzcnsjaHRFpmlxpJ4S3NT64qtEKYweNTUMb/vh0OMQ=
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.0.1 h1:vRla3taM+zkziP1NUGfN6Y6zJ9ZSSMg0fs/JhCGyX1s=
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.0.1/go.mod h1:eyFPc0lhFnNSpyCDb0ZkrB3Hbtqvn1K1JZmjo2BXqeo=
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.2.0 h1:2R/le0s/MZpHtc+ijuXKe2c4KGN14M85mWtGlmg6vec=
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.2.0/go.mod h1:fa/d1lAdUHxuc1jedx30ZfNG573oQTQmUni3N6pcW+0=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||
github.com/influxdata/influxql v1.1.0/go.mod h1:KpVI7okXjK6PRi3Z5B+mtKZli+R1DnZgb3N+tzevNgo=
|
||||
github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE=
|
||||
@@ -695,11 +713,12 @@ github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2
|
||||
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
|
||||
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
github.com/jonboulle/clockwork v0.2.1 h1:S/EaQvW6FpWMYAvYvY+OBDvpaM+izu0oiwo5y0MH7U0=
|
||||
github.com/jonboulle/clockwork v0.2.1/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
|
||||
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
github.com/joncrlsn/dque v2.2.1-0.20200515025108-956d14155fa2+incompatible/go.mod h1:hDZb8oMj3Kp8MxtbNLg9vrtAUDHjgI1yZvqivT4O8Iw=
|
||||
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
@@ -745,6 +764,8 @@ github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH6
|
||||
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
@@ -791,6 +812,8 @@ github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7
|
||||
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
|
||||
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
|
||||
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
|
||||
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201213122252-bcd7e1b9601e h1:qqXczln0qwkVGcpQ+sQuPOVntt2FytYarXXxYSNJkgw=
|
||||
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201213122252-bcd7e1b9601e/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
|
||||
github.com/mattetti/filebuffer v1.0.0 h1:ixTvQ0JjBTwWbdpDZ98lLrydo7KRi8xNRIi5RFszsbY=
|
||||
github.com/mattetti/filebuffer v1.0.0/go.mod h1:X6nyAIge2JGVmuJt2MFCqmHrb/5IHiphfHtot0s5cnI=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
@@ -915,6 +938,7 @@ github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zM
|
||||
github.com/opentracing-contrib/go-grpc v0.0.0-20180928155321-4b5a12d3ff02/go.mod h1:JNdpVEzCpXBgIiv4ds+TzhN1hrtxq6ClLrTlT9OQRSc=
|
||||
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
|
||||
github.com/opentracing-contrib/go-stdlib v0.0.0-20190519235532-cf7a6c988dc9/go.mod h1:PLldrQSroqzH70Xl+1DQcGnefIbqsKR7UDaiux3zV+w=
|
||||
github.com/opentracing-contrib/go-stdlib v1.0.0 h1:TBS7YuVotp8myLon4Pv7BtCBzOTo1DeZCld0Z63mW2w=
|
||||
github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU=
|
||||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
@@ -969,8 +993,9 @@ github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeD
|
||||
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
github.com/prometheus/client_golang v1.4.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4=
|
||||
github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.8.0 h1:zvJNkoCFAnYFNC24FV8nW4JdRJ3GIFcLbg65lL/JDcw=
|
||||
github.com/prometheus/client_golang v1.8.0/go.mod h1:O9VU6huf47PktckDQfMTX0Y8tY0/7TSWwj+ITvv0TnM=
|
||||
github.com/prometheus/client_model v0.0.0-20170216185247-6f3806018612/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
@@ -987,8 +1012,9 @@ github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+
|
||||
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
|
||||
github.com/prometheus/common v0.8.0/go.mod h1:PC/OgXc+UN7B4ALwvn1yzVZmVwvhXp5JsbBv6wSv6i0=
|
||||
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
||||
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.14.0 h1:RHRyE8UocrbjU+6UvRzwi6HjiDfxrrBU91TtbKzkGp4=
|
||||
github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
|
||||
github.com/prometheus/node_exporter v1.0.0-rc.0.0.20200428091818-01054558c289/go.mod h1:FGbBv5OPKjch+jNUJmEQpMZytIdyW0NdBtWFcfSKusc=
|
||||
github.com/prometheus/procfs v0.0.0-20180612222113-7d6f385de8be/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
@@ -1001,8 +1027,9 @@ github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa
|
||||
github.com/prometheus/procfs v0.0.6/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4=
|
||||
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/prometheus v0.0.0-20180315085919-58e2a31db8de/go.mod h1:oAIUtOny2rjMX0OWN5vPR5/q/twIROJvdqnQKDdil/s=
|
||||
github.com/prometheus/prometheus v0.0.0-20190818123050-43acd0e2e93f/go.mod h1:rMTlmxGCvukf2KMu3fClMDKLLoJ5hl61MhcJ7xKakf0=
|
||||
github.com/prometheus/prometheus v1.8.2-0.20200107122003-4708915ac6ef/go.mod h1:7U90zPoLkWjEIQcy/rweQla82OCTUzxVHE51G3OhJbI=
|
||||
@@ -1027,10 +1054,11 @@ github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7 h1:J4AOUcOh/t1XbQcJfkEqhzgvMJ2tDxdCVvmHxW5QXao=
|
||||
github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7/go.mod h1:Oz4y6ImuOQZxynhbSXk7btjEfNBtGlj2dcaOvXl2FSM=
|
||||
github.com/russellhaering/goxmldsig v0.0.0-20200902171629-2e1fbc2c5593 h1:wkyiSzH81tsd3tSoznvnXMIJo0cpHjbFuJhs/E9t/B8=
|
||||
github.com/russellhaering/goxmldsig v0.0.0-20200902171629-2e1fbc2c5593/go.mod h1:QK8GhXPB3+AfuCrfo0oRISa9NfzeCpWmxeGnqEpDF9o=
|
||||
github.com/russellhaering/goxmldsig v1.1.0 h1:lK/zeJie2sqG52ZAlPNn1oBBqsIsEKypUUBGpYYF6lk=
|
||||
github.com/russellhaering/goxmldsig v1.1.0/go.mod h1:QK8GhXPB3+AfuCrfo0oRISa9NfzeCpWmxeGnqEpDF9o=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
|
||||
@@ -1071,6 +1099,8 @@ github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
|
||||
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
|
||||
@@ -1105,7 +1135,6 @@ github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
@@ -1127,6 +1156,7 @@ github.com/ua-parser/uap-go v0.0.0-20190826212731-daf92ba38329 h1:VBsKFh4W1JEMz3
|
||||
github.com/ua-parser/uap-go v0.0.0-20190826212731-daf92ba38329/go.mod h1:OBcG9bn7sHtXgarhUEb3OfCnNsgtGnkVf41ilSZ3K3E=
|
||||
github.com/uber/jaeger-client-go v2.15.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||
github.com/uber/jaeger-client-go v2.20.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||
github.com/uber/jaeger-client-go v2.22.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||
github.com/uber/jaeger-client-go v2.23.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||
github.com/uber/jaeger-client-go v2.24.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||
github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24szfsn/3LvK9QHCq9oQw8+U=
|
||||
@@ -1151,6 +1181,9 @@ github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPU
|
||||
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
|
||||
github.com/weaveworks/common v0.0.0-20200206153930-760e36ae819a/go.mod h1:6enWAqfQBFrE8X/XdJwZr8IKgh1chStuFR0mjU/UOUw=
|
||||
github.com/weaveworks/common v0.0.0-20200625145055-4b1847531bc9/go.mod h1:c98fKi5B9u8OsKGiWHLRKus6ToQ1Tubeow44ECO1uxY=
|
||||
github.com/weaveworks/common v0.0.0-20201119133501-0619918236ec h1:5JmevdpzK10Z2ua0VDToj7Kg2+/t0FzdYBjsurYRE8k=
|
||||
github.com/weaveworks/common v0.0.0-20201119133501-0619918236ec/go.mod h1:ykzWac1LtVfOxdCK+jD754at1Ws9dKCwFeUzkFBffPs=
|
||||
github.com/weaveworks/promrus v1.2.0 h1:jOLf6pe6/vss4qGHjXmGz4oDJQA+AOCqEL3FvvZGz7M=
|
||||
github.com/weaveworks/promrus v1.2.0/go.mod h1:SaE82+OJ91yqjrE1rsvBWVzNZKcHYFtMUyS1+Ogs/KA=
|
||||
github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||
github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs=
|
||||
@@ -1171,6 +1204,7 @@ github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZ
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/zenazn/goji v0.9.1-0.20160507202103-64eb34159fe5/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||
@@ -1198,6 +1232,8 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
@@ -1242,6 +1278,8 @@ golang.org/x/crypto v0.0.0-20200422194213-44a606286825/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 h1:sYNJzB4J8toYPQTM6pAkcmBRgw9SnQKP9oXCHfgy604=
|
||||
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -1330,8 +1368,10 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4=
|
||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201022231255-08b38378de70 h1:Z6x4N9mAi4oF0TbHweCsH618MO6OI6UFgV0FP5n0wBY=
|
||||
golang.org/x/net v0.0.0-20201022231255-08b38378de70/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1340,6 +1380,8 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 h1:ld7aEMNHoBnnDAX15v1T6z31v8HwR2A9FYOuAhWqkwc=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -1350,6 +1392,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1417,12 +1461,19 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d h1:QQrM/CCYEzTs91GZylDCQjGHudbPTxF/1fvXdVh5lMo=
|
||||
golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPMJTiBfWycGh/u3UoO88=
|
||||
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd h1:WgqgiQvkiZWz7XLhphjt2GI2GcGCTIZs9jqXMWmH+oc=
|
||||
golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1436,6 +1487,7 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s=
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -1503,15 +1555,24 @@ golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWc
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200513201620-d5fe73897c97/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200603131246-cc40288be839/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200725200936-102e7d357031 h1:VtIxiVHWPhnny2ZTi4f9/2diZKqyLaq3FUTuud5+khA=
|
||||
golang.org/x/tools v0.0.0-20200725200936-102e7d357031/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201023150057-2f4fa188d925 h1:iGGR3nU1TUd+WTF17QPTTShBEDG66IKsDIDKtC4EseY=
|
||||
golang.org/x/tools v0.0.0-20201023150057-2f4fa188d925/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
|
||||
gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
|
||||
gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU=
|
||||
@@ -1537,6 +1598,9 @@ google.golang.org/api v0.26.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0 h1:BaiDisFir8O4IJxvAabCGGkQ6yCJegNQqSVoYUNAnbk=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.33.0 h1:+gL0XvACeMIvpwLZ5rQZzLn5cwOsgg8dIcfJ2SYfBVw=
|
||||
google.golang.org/api v0.33.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
@@ -1547,6 +1611,8 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180608181217-32ee49c4dd80/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
@@ -1580,12 +1646,19 @@ google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfG
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200603110839-e855014d5736/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200710124503-20a17af7bd0e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200724131911-43cab4749ae7 h1:AWgNCmk2V5HZp9AiCDRBExX/b9I0Ey9F8STHDZlhCC4=
|
||||
google.golang.org/genproto v0.0.0-20200724131911-43cab4749ae7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5 h1:YejJbGvoWsTXHab4OKNrzk27Dr7s4lPLnewbHue1+gM=
|
||||
google.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
@@ -1608,6 +1681,10 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0 h1:M5a8xTlYTxwMn5ZFkwhRabsygDY5G8TYLyQDBxJNAxE=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -1682,6 +1759,7 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
|
||||
k8s.io/api v0.0.0-20190813020757-36bff7324fb7/go.mod h1:3Iy+myeAORNCLgjd/Xu9ebwN7Vh59Bw0vh9jhoX+V58=
|
||||
k8s.io/api v0.0.0-20191115095533-47f6de673b26/go.mod h1:iA/8arsvelvo4IDqIhX4IbjTEKBGgvsf2OraTuRtLFU=
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"packages": ["packages/*"],
|
||||
"version": "7.3.0-beta.2"
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "7.3.5"
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"private": true,
|
||||
"name": "grafana",
|
||||
"version": "7.3.0-beta2",
|
||||
"version": "7.3.6",
|
||||
"repository": "github:grafana/grafana",
|
||||
"scripts": {
|
||||
"api-tests": "jest --notify --watch --config=devenv/e2e-api-tests/jest.js",
|
||||
@@ -46,7 +46,7 @@
|
||||
},
|
||||
"grafana": {
|
||||
"whatsNewUrl": "https://grafana.com/docs/grafana/latest/guides/whats-new-in-v7-3/",
|
||||
"releaseNotesUrl": "https://community.grafana.com/t/release-notes-v7-3-x/37993"
|
||||
"releaseNotesUrl": "https://grafana.com/docs/grafana/latest/release-notes/"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/data",
|
||||
"version": "7.3.0-beta.2",
|
||||
"version": "7.3.5",
|
||||
"description": "Grafana Data Library",
|
||||
"keywords": [
|
||||
"typescript"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getDisplayProcessor, getRawDisplayProcessor } from './displayProcessor';
|
||||
import { getDecimalsForValue, getDisplayProcessor, getRawDisplayProcessor } from './displayProcessor';
|
||||
import { DisplayProcessor, DisplayValue } from '../types/displayValue';
|
||||
import { MappingType, ValueMapping } from '../types/valueMapping';
|
||||
import { FieldConfig, FieldType, ThresholdsMode } from '../types';
|
||||
@@ -16,9 +16,8 @@ function getDisplayProcessorFromConfig(config: FieldConfig) {
|
||||
function assertSame(input: any, processors: DisplayProcessor[], match: DisplayValue) {
|
||||
processors.forEach(processor => {
|
||||
const value = processor(input);
|
||||
expect(value.text).toEqual(match.text);
|
||||
if (match.hasOwnProperty('numeric')) {
|
||||
expect(value.numeric).toEqual(match.numeric);
|
||||
for (const key of Object.keys(match)) {
|
||||
expect((value as any)[key]).toEqual((match as any)[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -89,6 +88,27 @@ describe('Process simple display values', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Process null values', () => {
|
||||
const processors = [
|
||||
getDisplayProcessorFromConfig({
|
||||
min: 0,
|
||||
max: 100,
|
||||
thresholds: {
|
||||
mode: ThresholdsMode.Absolute,
|
||||
steps: [
|
||||
{ value: -Infinity, color: '#000' },
|
||||
{ value: 0, color: '#100' },
|
||||
{ value: 100, color: '#200' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
it('Null should get -Infinity (base) color', () => {
|
||||
assertSame(null, processors, { text: '', numeric: NaN, color: '#000' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Format value', () => {
|
||||
it('should return if value isNaN', () => {
|
||||
const valueMappings: ValueMapping[] = [];
|
||||
@@ -329,3 +349,21 @@ describe('getRawDisplayProcessor', () => {
|
||||
expect(result).toEqual({ text: expected, numeric: null });
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDecimalsForValue', () => {
|
||||
it.each`
|
||||
value | expected
|
||||
${0} | ${0}
|
||||
${13.37} | ${0}
|
||||
${-13.37} | ${0}
|
||||
${12679.3712345811212} | ${0}
|
||||
${-12679.3712345811212} | ${0}
|
||||
${0.3712345} | ${2}
|
||||
${-0.37123458} | ${2}
|
||||
${-0.04671994403853774} | ${3}
|
||||
${0.04671994403853774} | ${3}
|
||||
`('should return correct suggested decimal count', ({ value, expected }) => {
|
||||
const result = getDecimalsForValue(value);
|
||||
expect(result.decimals).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -115,7 +115,7 @@ export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayP
|
||||
}
|
||||
}
|
||||
|
||||
return { text, numeric, prefix, suffix, ...scaleFunc(0) };
|
||||
return { text, numeric, prefix, suffix, ...scaleFunc(-Infinity) };
|
||||
};
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ export function getDecimalsForValue(value: number, decimalOverride?: DecimalCoun
|
||||
return { decimals: decimalOverride, scaledDecimals: null };
|
||||
}
|
||||
|
||||
let dec = -Math.floor(Math.log(value) / Math.LN10) + 1;
|
||||
let dec = -Math.floor(Math.log(Math.abs(value)) / Math.LN10) + 1;
|
||||
const magn = Math.pow(10, -dec);
|
||||
const norm = value / magn; // norm is between 1.0 and 10.0
|
||||
let size;
|
||||
|
||||
@@ -7,18 +7,18 @@ import {
|
||||
setDynamicConfigValue,
|
||||
setFieldConfigDefaults,
|
||||
} from './fieldOverrides';
|
||||
import { MutableDataFrame, toDataFrame } from '../dataframe';
|
||||
import { MutableDataFrame, toDataFrame, ArrayDataFrame } from '../dataframe';
|
||||
import {
|
||||
DataFrame,
|
||||
Field,
|
||||
FieldColorModeId,
|
||||
FieldConfig,
|
||||
FieldConfigPropertyItem,
|
||||
FieldConfigSource,
|
||||
FieldType,
|
||||
InterpolateFunction,
|
||||
ThresholdsMode,
|
||||
FieldColorModeId,
|
||||
ScopedVars,
|
||||
ThresholdsMode,
|
||||
} from '../types';
|
||||
import { locationUtil, Registry } from '../utils';
|
||||
import { mockStandardProperties } from '../utils/tests/mockStandardProperties';
|
||||
@@ -87,6 +87,54 @@ describe('Global MinMax', () => {
|
||||
expect(minmax.min).toEqual(-20);
|
||||
expect(minmax.max).toEqual(1234);
|
||||
});
|
||||
|
||||
it('find global min max when all values are zero', () => {
|
||||
const f0 = new ArrayDataFrame<{ title: string; value: number; value2: number | null }>([
|
||||
{ title: 'AAA', value: 0, value2: 0 },
|
||||
{ title: 'CCC', value: 0, value2: 0 },
|
||||
]);
|
||||
|
||||
const minmax = findNumericFieldMinMax([f0]);
|
||||
expect(minmax.min).toEqual(0);
|
||||
expect(minmax.max).toEqual(0);
|
||||
});
|
||||
|
||||
describe('when value is null', () => {
|
||||
it('then global min max should be null', () => {
|
||||
const frame = toDataFrame({
|
||||
fields: [
|
||||
{ name: 'Time', type: FieldType.time, values: [1] },
|
||||
{ name: 'Value', type: FieldType.number, values: [null] },
|
||||
],
|
||||
});
|
||||
const { min, max } = findNumericFieldMinMax([frame]);
|
||||
|
||||
expect(min).toBe(null);
|
||||
expect(max).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when value values are zeo', () => {
|
||||
it('then global min max should be correct', () => {
|
||||
const frame = toDataFrame({
|
||||
fields: [
|
||||
{ name: 'Time', type: FieldType.time, values: [1, 2] },
|
||||
{ name: 'Value', type: FieldType.number, values: [1, 2] },
|
||||
],
|
||||
});
|
||||
const frame2 = toDataFrame({
|
||||
fields: [
|
||||
{ name: 'Time', type: FieldType.time, values: [1, 2] },
|
||||
{ name: 'Value', type: FieldType.number, values: [0, 0] },
|
||||
],
|
||||
});
|
||||
|
||||
const { min, max } = findNumericFieldMinMax([frame, frame2]);
|
||||
|
||||
expect(min).toBe(0);
|
||||
expect(max).toBe(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyFieldOverrides', () => {
|
||||
|
||||
@@ -41,13 +41,13 @@ interface OverrideProps {
|
||||
}
|
||||
|
||||
interface GlobalMinMax {
|
||||
min: number;
|
||||
max: number;
|
||||
min?: number | null;
|
||||
max?: number | null;
|
||||
}
|
||||
|
||||
export function findNumericFieldMinMax(data: DataFrame[]): GlobalMinMax {
|
||||
let min = Number.MAX_VALUE;
|
||||
let max = Number.MIN_VALUE;
|
||||
let min: number | null = null;
|
||||
let max: number | null = null;
|
||||
|
||||
const reducers = [ReducerID.min, ReducerID.max];
|
||||
|
||||
@@ -55,11 +55,15 @@ export function findNumericFieldMinMax(data: DataFrame[]): GlobalMinMax {
|
||||
for (const field of frame.fields) {
|
||||
if (field.type === FieldType.number) {
|
||||
const stats = reduceField({ field, reducers });
|
||||
if (stats[ReducerID.min] < min) {
|
||||
min = stats[ReducerID.min];
|
||||
const statsMin = stats[ReducerID.min];
|
||||
const statsMax = stats[ReducerID.max];
|
||||
|
||||
if (min === null || statsMin < min) {
|
||||
min = statsMin;
|
||||
}
|
||||
if (stats[ReducerID.max] > max) {
|
||||
max = stats[ReducerID.max];
|
||||
|
||||
if (max === null || statsMax > max) {
|
||||
max = statsMax;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,12 @@ export function getScaleCalculator(field: Field, theme: GrafanaTheme): ScaleCalc
|
||||
const info = getMinMaxAndDelta(field);
|
||||
|
||||
return (value: number) => {
|
||||
const percent = (value - info.min!) / info.delta;
|
||||
let percent = 0;
|
||||
|
||||
if (value !== -Infinity) {
|
||||
percent = (value - info.min!) / info.delta;
|
||||
}
|
||||
|
||||
const threshold = getActiveThresholdForValue(field, value, percent);
|
||||
|
||||
return {
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface AnnotationQuery<TQuery extends DataQuery = DataQuery> {
|
||||
enable: boolean;
|
||||
name: string;
|
||||
iconColor: string;
|
||||
hide?: boolean;
|
||||
|
||||
// Standard datasource query
|
||||
target?: TQuery;
|
||||
|
||||
@@ -55,6 +55,8 @@ export interface LicenseInfo {
|
||||
expiry: number;
|
||||
licenseUrl: string;
|
||||
stateInfo: string;
|
||||
hasValidLicense: boolean;
|
||||
edition: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -114,6 +114,7 @@ export interface DataSourcePluginMeta<T extends KeyValue = {}> extends PluginMet
|
||||
queryOptions?: PluginMetaQueryOptions;
|
||||
sort?: number;
|
||||
streaming?: boolean;
|
||||
unlicensed?: boolean;
|
||||
}
|
||||
|
||||
interface PluginMetaQueryOptions {
|
||||
@@ -599,6 +600,6 @@ export abstract class LanguageProvider {
|
||||
* Returns startTask that resolves with a task list when main syntax is loaded.
|
||||
* Task list consists of secondary promises that load more detailed language features.
|
||||
*/
|
||||
abstract start: () => Promise<any[]>;
|
||||
abstract start: () => Promise<Array<Promise<any>>>;
|
||||
startTask?: Promise<any[]>;
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@ import { ComponentClass } from 'react';
|
||||
import { KeyValue } from './data';
|
||||
import { LiveChannelSupport } from './live';
|
||||
|
||||
/** Describes plugins life cycle status */
|
||||
export enum PluginState {
|
||||
alpha = 'alpha', // Only included it `enable_alpha` is true
|
||||
alpha = 'alpha', // Only included if `enable_alpha` config option is true
|
||||
beta = 'beta', // Will show a warning banner
|
||||
deprecated = 'deprecated', // Will continue to work -- but not show up in the options to add
|
||||
}
|
||||
|
||||
/** Describes {@link https://grafana.com/docs/grafana/latest/plugins | type of plugin} */
|
||||
export enum PluginType {
|
||||
panel = 'panel',
|
||||
datasource = 'datasource',
|
||||
@@ -15,12 +17,26 @@ export enum PluginType {
|
||||
renderer = 'renderer',
|
||||
}
|
||||
|
||||
/** Describes status of {@link https://grafana.com/docs/grafana/latest/plugins/plugin-signatures/ | plugin signature} */
|
||||
export enum PluginSignatureStatus {
|
||||
internal = 'internal', // core plugin, no signature
|
||||
valid = 'valid', // signed and accurate MANIFEST
|
||||
invalid = 'invalid', // invalid signature
|
||||
modified = 'modified', // valid signature, but content mismatch
|
||||
unsigned = 'unsigned', // no MANIFEST file
|
||||
missing = 'missing', // missing signature file
|
||||
}
|
||||
|
||||
/** Describes error code returned from Grafana plugins API call */
|
||||
export enum PluginErrorCode {
|
||||
missingSignature = 'signatureMissing',
|
||||
invalidSignature = 'signatureInvalid',
|
||||
modifiedSignature = 'signatureModified',
|
||||
}
|
||||
|
||||
/** Describes error returned from Grafana plugins API call */
|
||||
export interface PluginError {
|
||||
errorCode: PluginErrorCode;
|
||||
pluginId: string;
|
||||
}
|
||||
|
||||
export interface PluginMeta<T extends KeyValue = {}> {
|
||||
|
||||
@@ -80,6 +80,10 @@ export type TraceData = {
|
||||
warnings?: string[] | null;
|
||||
};
|
||||
|
||||
export type TraceViewData = TraceData & {
|
||||
spans: TraceSpanData[];
|
||||
};
|
||||
|
||||
export type Trace = TraceData & {
|
||||
duration: number;
|
||||
endTime: number;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/e2e-selectors",
|
||||
"version": "7.3.0-beta.2",
|
||||
"version": "7.3.5",
|
||||
"description": "Grafana End-to-End Test Selectors Library",
|
||||
"keywords": [
|
||||
"cli",
|
||||
|
||||
@@ -135,4 +135,14 @@ export const Pages = {
|
||||
SoloPanel: {
|
||||
url: (page: string) => `/d-solo/${page}`,
|
||||
},
|
||||
PluginsList: {
|
||||
page: 'Plugins list page',
|
||||
list: 'Plugins list',
|
||||
listItem: 'Plugins list item',
|
||||
signatureErrorNotice: 'Unsigned plugins notice',
|
||||
},
|
||||
PluginPage: {
|
||||
page: 'Plugin page',
|
||||
signatureInfo: 'Plugin signature info',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/e2e",
|
||||
"version": "7.3.0-beta.2",
|
||||
"version": "7.3.5",
|
||||
"description": "Grafana End-to-End Test Library",
|
||||
"keywords": [
|
||||
"cli",
|
||||
@@ -44,7 +44,7 @@
|
||||
"types": "src/index.ts",
|
||||
"dependencies": {
|
||||
"@cypress/webpack-preprocessor": "4.1.3",
|
||||
"@grafana/e2e-selectors": "7.3.0-beta.2",
|
||||
"@grafana/e2e-selectors": "7.3.5",
|
||||
"@grafana/tsconfig": "^1.0.0-rc1",
|
||||
"@mochajs/json-file-reporter": "^1.2.0",
|
||||
"blink-diff": "1.0.13",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/runtime",
|
||||
"version": "7.3.0-beta.2",
|
||||
"version": "7.3.5",
|
||||
"description": "Grafana Runtime Library",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -22,8 +22,8 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grafana/data": "7.3.0-beta.2",
|
||||
"@grafana/ui": "7.3.0-beta.2",
|
||||
"@grafana/data": "7.3.5",
|
||||
"@grafana/ui": "7.3.5",
|
||||
"systemjs": "0.20.19",
|
||||
"systemjs-plugin-css": "0.1.37"
|
||||
},
|
||||
|
||||
@@ -62,6 +62,7 @@ export class GrafanaBootConfig implements GrafanaConfig {
|
||||
rendererAvailable = false;
|
||||
http2Enabled = false;
|
||||
dateFormats?: SystemDateFormatSettings;
|
||||
marketplaceUrl?: string;
|
||||
|
||||
constructor(options: GrafanaBootConfig) {
|
||||
this.theme = options.bootData.user.lightTheme ? getTheme(GrafanaThemeType.Light) : getTheme(GrafanaThemeType.Dark);
|
||||
|
||||
@@ -43,7 +43,7 @@ get_file "https://codeclimate.com/downloads/test-reporter/test-reporter-latest-l
|
||||
"b4138199aa755ebfe171b57cc46910b13258ace5fbc4eaa099c42607cd0bff32"
|
||||
chmod +x /usr/local/bin/cc-test-reporter
|
||||
|
||||
curl -fL -o /usr/local/bin/grabpl "https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.16/grabpl"
|
||||
curl -fL -o /usr/local/bin/grabpl "https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.20/grabpl"
|
||||
|
||||
apk add --no-cache git
|
||||
# Install Mage
|
||||
|
||||
@@ -44,7 +44,7 @@ get_file "https://codeclimate.com/downloads/test-reporter/test-reporter-latest-l
|
||||
"b4138199aa755ebfe171b57cc46910b13258ace5fbc4eaa099c42607cd0bff32"
|
||||
chmod 755 /usr/local/bin/cc-test-reporter
|
||||
|
||||
wget -O /usr/local/bin/grabpl "https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.16/grabpl"
|
||||
wget -O /usr/local/bin/grabpl "https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.20/grabpl"
|
||||
chmod +x /usr/local/bin/grabpl
|
||||
|
||||
# Install Mage
|
||||
|
||||
@@ -27,7 +27,7 @@ get_file "https://codeclimate.com/downloads/test-reporter/test-reporter-latest-l
|
||||
"b4138199aa755ebfe171b57cc46910b13258ace5fbc4eaa099c42607cd0bff32"
|
||||
chmod +x /usr/local/bin/cc-test-reporter
|
||||
|
||||
wget -O /usr/local/bin/grabpl "https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.16/grabpl"
|
||||
wget -O /usr/local/bin/grabpl "https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.20/grabpl"
|
||||
chmod +x /usr/local/bin/grabpl
|
||||
|
||||
# Install Mage
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/toolkit",
|
||||
"version": "7.3.0-beta.2",
|
||||
"version": "7.3.5",
|
||||
"description": "Grafana Toolkit",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
|
||||
@@ -97,6 +97,7 @@ const getCommonPlugins = (options: WebpackConfigurationOptions) => {
|
||||
{ from: hasREADME ? 'README.md' : '../README.md', to: '.', force: true },
|
||||
{ from: 'plugin.json', to: '.' },
|
||||
{ from: '../LICENSE', to: '.' },
|
||||
{ from: '../CHANGELOG.md', to: '.', force: true },
|
||||
{ from: '**/*.json', to: '.' },
|
||||
{ from: '**/*.svg', to: '.' },
|
||||
{ from: '**/*.png', to: '.' },
|
||||
|
||||
@@ -103,7 +103,7 @@ module.exports = ({ config, mode }) => {
|
||||
minimize: isProductionBuild,
|
||||
minimizer: isProductionBuild
|
||||
? [
|
||||
new TerserPlugin({ cache: false, parallel: false, sourceMap: false, exclude: /monaco/ }),
|
||||
new TerserPlugin({ cache: false, parallel: false, sourceMap: false, exclude: /monaco|bizcharts/ }),
|
||||
new OptimizeCSSAssetsPlugin({}),
|
||||
]
|
||||
: [],
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/ui",
|
||||
"version": "7.3.0-beta.2",
|
||||
"version": "7.3.5",
|
||||
"description": "Grafana Components Library",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -27,8 +27,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/core": "^10.0.27",
|
||||
"@grafana/data": "7.3.0-beta.2",
|
||||
"@grafana/e2e-selectors": "7.3.0-beta.2",
|
||||
"@grafana/data": "7.3.5",
|
||||
"@grafana/e2e-selectors": "7.3.5",
|
||||
"@grafana/slate-react": "0.22.9-grafana",
|
||||
"@grafana/tsconfig": "^1.0.0-rc1",
|
||||
"@iconscout/react-unicons": "1.1.4",
|
||||
|
||||
@@ -5,6 +5,7 @@ import { selectors } from '@grafana/e2e-selectors';
|
||||
import { useTheme } from '../../themes';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { IconName } from '../../types/icon';
|
||||
import { getColorsFromSeverity } from '../../utils/colors';
|
||||
|
||||
export type AlertVariant = 'success' | 'warning' | 'error' | 'info';
|
||||
|
||||
@@ -76,21 +77,11 @@ export const Alert: FC<Props> = ({
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme, severity: AlertVariant, outline: boolean) => {
|
||||
const { redBase, redShade, greenBase, greenShade, blue80, blue77, white } = theme.palette;
|
||||
const backgrounds = {
|
||||
error: css`
|
||||
background: linear-gradient(90deg, ${redBase}, ${redShade});
|
||||
`,
|
||||
warning: css`
|
||||
background: linear-gradient(90deg, ${redBase}, ${redShade});
|
||||
`,
|
||||
info: css`
|
||||
background: linear-gradient(100deg, ${blue80}, ${blue77});
|
||||
`,
|
||||
success: css`
|
||||
background: linear-gradient(100deg, ${greenBase}, ${greenShade});
|
||||
`,
|
||||
};
|
||||
const { white } = theme.palette;
|
||||
const severityColors = getColorsFromSeverity(severity, theme);
|
||||
const background = css`
|
||||
background: linear-gradient(90deg, ${severityColors[0]}, ${severityColors[0]});
|
||||
`;
|
||||
|
||||
return {
|
||||
container: css`
|
||||
@@ -106,7 +97,7 @@ const getStyles = (theme: GrafanaTheme, severity: AlertVariant, outline: boolean
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
${backgrounds[severity]}
|
||||
${background}
|
||||
`,
|
||||
icon: css`
|
||||
padding: 0 ${theme.spacing.md} 0 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { HTMLAttributes } from 'react';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { useTheme } from '../../themes/ThemeContext';
|
||||
import { stylesFactory } from '../../themes/stylesFactory';
|
||||
@@ -6,23 +6,23 @@ import { IconName } from '../../types';
|
||||
import { Tooltip } from '../Tooltip/Tooltip';
|
||||
import { getColorForTheme, GrafanaTheme } from '@grafana/data';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { css } from 'emotion';
|
||||
import { HorizontalGroup } from '..';
|
||||
import { css, cx } from 'emotion';
|
||||
import { HorizontalGroup } from '../Layout/Layout';
|
||||
|
||||
export type BadgeColor = 'blue' | 'red' | 'green' | 'orange' | 'purple';
|
||||
|
||||
export interface BadgeProps {
|
||||
export interface BadgeProps extends HTMLAttributes<HTMLDivElement> {
|
||||
text: string;
|
||||
color: BadgeColor;
|
||||
icon?: IconName;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
export const Badge = React.memo<BadgeProps>(({ icon, color, text, tooltip }) => {
|
||||
export const Badge = React.memo<BadgeProps>(({ icon, color, text, tooltip, className, ...otherProps }) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyles(theme, color);
|
||||
const badge = (
|
||||
<div className={styles.wrapper}>
|
||||
<div className={cx(styles.wrapper, className)} {...otherProps}>
|
||||
<HorizontalGroup align="center" spacing="xs">
|
||||
{icon && <Icon name={icon} size="sm" />}
|
||||
<span>{text}</span>
|
||||
|
||||
@@ -63,6 +63,8 @@ export abstract class BigValueLayout {
|
||||
fontSize: this.valueFontSize,
|
||||
fontWeight: 500,
|
||||
lineHeight: LINE_HEIGHT,
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
};
|
||||
|
||||
switch (this.props.colorMode) {
|
||||
|
||||
@@ -30,10 +30,12 @@ exports[`BigValue Render with basic options should render 1`] = `
|
||||
<FormattedDisplayValue
|
||||
style={
|
||||
Object {
|
||||
"color": "#202226",
|
||||
"color": "#f7f8fa",
|
||||
"fontSize": 230,
|
||||
"fontWeight": 500,
|
||||
"lineHeight": 1.2,
|
||||
"position": "relative",
|
||||
"zIndex": 1,
|
||||
}
|
||||
}
|
||||
value={
|
||||
|
||||
@@ -13,7 +13,7 @@ It is used in a `ConfigEditor` for data source plugins. You can find more exampl
|
||||
### Example usage
|
||||
```jsx
|
||||
export const ConfigEditor = (props: Props) => {
|
||||
const { options, onOptionsChange, config } = props;
|
||||
const { options, onOptionsChange } = props;
|
||||
return (
|
||||
<>
|
||||
<DataSourceHttpSettings
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { HttpSettingsProps } from './types';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Button, InlineFormLabel, Input } from '..';
|
||||
import Select from '../Forms/Legacy/Select/Select';
|
||||
|
||||
export const SigV4AuthSettings: React.FC<HttpSettingsProps> = props => {
|
||||
const { dataSourceConfig } = props;
|
||||
const { dataSourceConfig, onChange } = props;
|
||||
|
||||
const authProviderOptions = [
|
||||
{ label: 'AWS SDK Default', value: 'default' },
|
||||
@@ -42,6 +42,12 @@ export const SigV4AuthSettings: React.FC<HttpSettingsProps> = props => {
|
||||
{ value: 'us-west-2', label: 'us-west-2' },
|
||||
] as SelectableValue[];
|
||||
|
||||
// Apply some defaults on initial render
|
||||
useEffect(() => {
|
||||
const sigV4AuthType = dataSourceConfig.jsonData.sigV4AuthType || 'default';
|
||||
onJsonDataChange('sigV4AuthType', sigV4AuthType);
|
||||
}, []);
|
||||
|
||||
const onSecureJsonDataReset = (fieldName: string) => {
|
||||
const state = {
|
||||
...dataSourceConfig,
|
||||
@@ -55,7 +61,7 @@ export const SigV4AuthSettings: React.FC<HttpSettingsProps> = props => {
|
||||
},
|
||||
};
|
||||
|
||||
props.onChange(state);
|
||||
onChange(state);
|
||||
};
|
||||
|
||||
const onSecureJsonDataChange = (fieldName: string, fieldValue: string) => {
|
||||
@@ -67,7 +73,7 @@ export const SigV4AuthSettings: React.FC<HttpSettingsProps> = props => {
|
||||
},
|
||||
};
|
||||
|
||||
props.onChange(state);
|
||||
onChange(state);
|
||||
};
|
||||
|
||||
const onJsonDataChange = (fieldName: string, fieldValue: string) => {
|
||||
@@ -79,7 +85,7 @@ export const SigV4AuthSettings: React.FC<HttpSettingsProps> = props => {
|
||||
},
|
||||
};
|
||||
|
||||
props.onChange(state);
|
||||
onChange(state);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -100,7 +106,7 @@ export const SigV4AuthSettings: React.FC<HttpSettingsProps> = props => {
|
||||
authProvider => authProvider.value === dataSourceConfig.jsonData.sigV4AuthType
|
||||
)}
|
||||
options={authProviderOptions}
|
||||
defaultValue={dataSourceConfig.jsonData.sigV4AuthType || 'keys'}
|
||||
defaultValue={dataSourceConfig.jsonData.sigV4AuthType || ''}
|
||||
onChange={option => {
|
||||
onJsonDataChange('sigV4AuthType', option.value);
|
||||
}}
|
||||
|
||||
@@ -25,8 +25,6 @@ export interface Props extends Themeable {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const FONT_SCALE = 1;
|
||||
|
||||
export class Gauge extends PureComponent<Props> {
|
||||
canvasElement: any;
|
||||
|
||||
@@ -95,13 +93,6 @@ export class Gauge extends PureComponent<Props> {
|
||||
return formatted;
|
||||
}
|
||||
|
||||
getFontScale(length: number): number {
|
||||
if (length > 12) {
|
||||
return FONT_SCALE - (length * 5) / 110;
|
||||
}
|
||||
return FONT_SCALE - (length * 5) / 101;
|
||||
}
|
||||
|
||||
draw() {
|
||||
const { field, showThresholdLabels, showThresholdMarkers, width, height, theme, value } = this.props;
|
||||
|
||||
@@ -112,7 +103,12 @@ export class Gauge extends PureComponent<Props> {
|
||||
const gaugeWidth = Math.min(dimension / 5.5, 40) / gaugeWidthReduceRatio;
|
||||
const thresholdMarkersWidth = gaugeWidth / 5;
|
||||
const text = formattedValueToString(value);
|
||||
const fontSize = calculateFontSize(text, dimension - gaugeWidth * 3, dimension, 1, 48);
|
||||
// This not 100% accurate as I am unsure of flot's calculations here
|
||||
const valueWidthBase = Math.min(width, dimension * 1.3) * 0.9;
|
||||
// remove gauge & marker width (on left and right side)
|
||||
// and 10px is some padding that flot adds to the outer canvas
|
||||
const valueWidth = valueWidthBase - ((gaugeWidth + (showThresholdMarkers ? thresholdMarkersWidth : 0)) * 2 + 10);
|
||||
const fontSize = calculateFontSize(text, valueWidth, dimension, 1, 48);
|
||||
const thresholdLabelFontSize = fontSize / 2.5;
|
||||
|
||||
let min = field.min!;
|
||||
|
||||
@@ -7,13 +7,22 @@ import { IconButton } from '../IconButton/IconButton';
|
||||
import { HorizontalGroup } from '../Layout/Layout';
|
||||
import panelArtDark from './panelArt_dark.svg';
|
||||
import panelArtLight from './panelArt_light.svg';
|
||||
import { AlertVariant } from '../Alert/Alert';
|
||||
import { getColorsFromSeverity } from '../../utils/colors';
|
||||
|
||||
export interface InfoBoxProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {
|
||||
children: React.ReactNode;
|
||||
/** Title of the box */
|
||||
title?: string | JSX.Element;
|
||||
/** Url of the read more link */
|
||||
url?: string;
|
||||
/** Text of the read more link */
|
||||
urlTitle?: string;
|
||||
/** Indicates whether or not box should be rendered with Grafana branding background */
|
||||
branded?: boolean;
|
||||
/** Color variant of the box */
|
||||
severity?: AlertVariant;
|
||||
/** Call back to be performed when box is dismissed */
|
||||
onDismiss?: () => void;
|
||||
}
|
||||
|
||||
@@ -24,9 +33,9 @@ export interface InfoBoxProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
|
||||
*/
|
||||
export const InfoBox = React.memo(
|
||||
React.forwardRef<HTMLDivElement, InfoBoxProps>(
|
||||
({ title, className, children, branded, url, urlTitle, onDismiss, ...otherProps }, ref) => {
|
||||
({ title, className, children, branded, url, urlTitle, onDismiss, severity = 'info', ...otherProps }, ref) => {
|
||||
const theme = useTheme();
|
||||
const styles = getInfoBoxStyles(theme);
|
||||
const styles = getInfoBoxStyles(theme, severity);
|
||||
const wrapperClassName = branded ? cx(styles.wrapperBranded, className) : cx(styles.wrapper, className);
|
||||
|
||||
return (
|
||||
@@ -49,18 +58,15 @@ export const InfoBox = React.memo(
|
||||
)
|
||||
);
|
||||
|
||||
const getInfoBoxStyles = stylesFactory((theme: GrafanaTheme) => ({
|
||||
const getInfoBoxStyles = stylesFactory((theme: GrafanaTheme, severity: AlertVariant) => ({
|
||||
wrapper: css`
|
||||
position: relative;
|
||||
padding: ${theme.spacing.md};
|
||||
background-color: ${theme.colors.bg2};
|
||||
border-top: 3px solid ${theme.palette.blue80};
|
||||
border-top: 3px solid ${getColorsFromSeverity(severity, theme)[0]};
|
||||
margin-bottom: ${theme.spacing.md};
|
||||
flex-grow: 1;
|
||||
|
||||
ul {
|
||||
padding-left: ${theme.spacing.lg};
|
||||
}
|
||||
color: ${theme.colors.textSemiWeak};
|
||||
|
||||
code {
|
||||
@include font-family-monospace();
|
||||
@@ -109,5 +115,6 @@ const getInfoBoxStyles = stylesFactory((theme: GrafanaTheme) => ({
|
||||
display: inline-block;
|
||||
margin-top: ${theme.spacing.md};
|
||||
font-size: ${theme.typography.size.sm};
|
||||
color: ${theme.colors.textSemiWeak};
|
||||
`,
|
||||
}));
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface SelectCommonProps<T> {
|
||||
filterOption?: (option: SelectableValue, searchQuery: string) => boolean;
|
||||
/** Function for formatting the text that is displayed when creating a new value*/
|
||||
formatCreateLabel?: (input: string) => string;
|
||||
getOptionLabel?: (item: SelectableValue<T>) => string;
|
||||
getOptionLabel?: (item: SelectableValue<T>) => React.ReactNode;
|
||||
getOptionValue?: (item: SelectableValue<T>) => string;
|
||||
inputValue?: string;
|
||||
invalid?: boolean;
|
||||
|
||||
@@ -49,7 +49,7 @@ export const DefaultCell: FC<TableCellProps> = props => {
|
||||
{value}
|
||||
</a>
|
||||
)}
|
||||
{showFilters && cell.value && <FilterActions {...props} />}
|
||||
{showFilters && cell.value !== undefined && <FilterActions {...props} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,11 +2,13 @@ import React, { FC } from 'react';
|
||||
import { TableCellProps } from './types';
|
||||
|
||||
export const ImageCell: FC<TableCellProps> = props => {
|
||||
const { cell, tableStyles, cellProps } = props;
|
||||
const { field, cell, tableStyles, cellProps } = props;
|
||||
|
||||
const displayValue = field.display!(cell.value);
|
||||
|
||||
return (
|
||||
<div {...cellProps} className={tableStyles.cellContainer}>
|
||||
<img src={cell.value} className={tableStyles.imageCell} />
|
||||
<img src={displayValue.text} className={tableStyles.imageCell} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
getColumns,
|
||||
getFilteredOptions,
|
||||
getTextAlign,
|
||||
rowToFieldValue,
|
||||
sortOptions,
|
||||
valuesToOptions,
|
||||
} from './utils';
|
||||
@@ -71,28 +72,88 @@ describe('Table utils', () => {
|
||||
});
|
||||
|
||||
describe('filterByValue', () => {
|
||||
it.each`
|
||||
rows | id | filterValues | expected
|
||||
${[]} | ${'0'} | ${[{ value: 'a' }]} | ${[]}
|
||||
${[{ values: { 0: 'a' } }]} | ${'0'} | ${null} | ${[{ values: { 0: 'a' } }]}
|
||||
${[{ values: { 0: 'a' } }]} | ${'0'} | ${undefined} | ${[{ values: { 0: 'a' } }]}
|
||||
${[{ values: { 0: 'a' } }]} | ${'1'} | ${[{ value: 'b' }]} | ${[]}
|
||||
${[{ values: { 0: 'a' } }]} | ${'0'} | ${[{ value: 'a' }]} | ${[{ values: { 0: 'a' } }]}
|
||||
${[{ values: { 0: 'a' } }, { values: { 1: 'a' } }]} | ${'0'} | ${[{ value: 'a' }]} | ${[{ values: { 0: 'a' } }]}
|
||||
${[{ values: { 0: 'a' } }, { values: { 0: 'b' } }, { values: { 0: 'c' } }]} | ${'0'} | ${[{ value: 'a' }, { value: 'b' }]} | ${[{ values: { 0: 'a' } }, { values: { 0: 'b' } }]}
|
||||
`(
|
||||
"when called with rows: '$rows.toString()', id: '$id' and filterValues: '$filterValues' then result should be '$expected'",
|
||||
({ rows, id, filterValues, expected }) => {
|
||||
expect(filterByValue(rows, id, filterValues)).toEqual(expected);
|
||||
}
|
||||
);
|
||||
describe('happy path', () => {
|
||||
const field: any = { values: new ArrayVector(['a', 'aa', 'ab', 'b', 'ba', 'bb', 'c']) };
|
||||
const rows: any = [
|
||||
{ index: 0, values: { 0: 'a' } },
|
||||
{ index: 1, values: { 0: 'aa' } },
|
||||
{ index: 2, values: { 0: 'ab' } },
|
||||
{ index: 3, values: { 0: 'b' } },
|
||||
{ index: 4, values: { 0: 'ba' } },
|
||||
{ index: 5, values: { 0: 'bb' } },
|
||||
{ index: 6, values: { 0: 'c' } },
|
||||
];
|
||||
const filterValues = [{ value: 'a' }, { value: 'b' }, { value: 'c' }];
|
||||
|
||||
const result = filterByValue(field)(rows, '0', filterValues);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ index: 0, values: { 0: 'a' } },
|
||||
{ index: 3, values: { 0: 'b' } },
|
||||
{ index: 6, values: { 0: 'c' } },
|
||||
]);
|
||||
});
|
||||
|
||||
describe('fast exit cases', () => {
|
||||
describe('no rows', () => {
|
||||
it('should return empty array', () => {
|
||||
const field: any = { values: new ArrayVector(['a']) };
|
||||
const rows: any = [];
|
||||
const filterValues = [{ value: 'a' }];
|
||||
|
||||
const result = filterByValue(field)(rows, '', filterValues);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('no filterValues', () => {
|
||||
it('should return rows', () => {
|
||||
const field: any = { values: new ArrayVector(['a']) };
|
||||
const rows: any = [{}];
|
||||
const filterValues = undefined;
|
||||
|
||||
const result = filterByValue(field)(rows, '', filterValues);
|
||||
|
||||
expect(result).toEqual([{}]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('no field', () => {
|
||||
it('should return rows', () => {
|
||||
const field = undefined;
|
||||
const rows: any = [{}];
|
||||
const filterValues = [{ value: 'a' }];
|
||||
|
||||
const result = filterByValue(field)(rows, '', filterValues);
|
||||
|
||||
expect(result).toEqual([{}]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('missing id in values', () => {
|
||||
it('should return rows', () => {
|
||||
const field: any = { values: new ArrayVector(['a', 'b', 'c']) };
|
||||
const rows: any = [
|
||||
{ index: 0, values: { 0: 'a' } },
|
||||
{ index: 1, values: { 0: 'b' } },
|
||||
{ index: 2, values: { 0: 'c' } },
|
||||
];
|
||||
const filterValues = [{ value: 'a' }, { value: 'b' }, { value: 'c' }];
|
||||
|
||||
const result = filterByValue(field)(rows, '1', filterValues);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateUniqueFieldValues', () => {
|
||||
describe('when called without field', () => {
|
||||
it('then it should return an empty object', () => {
|
||||
const field = undefined;
|
||||
const rows = [{ id: 0 }];
|
||||
const rows = [{ index: 0 }];
|
||||
|
||||
const result = calculateUniqueFieldValues(rows, field);
|
||||
|
||||
@@ -142,15 +203,15 @@ describe('Table utils', () => {
|
||||
text: `${value}.0`,
|
||||
})),
|
||||
};
|
||||
const rows: any[] = [{ id: 0 }, { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }];
|
||||
const rows: any[] = [{ index: 0 }, { index: 1 }, { index: 2 }, { index: 3 }, { index: 4 }];
|
||||
|
||||
const result = calculateUniqueFieldValues(rows, field);
|
||||
|
||||
expect(field.display).toHaveBeenCalledTimes(5);
|
||||
expect(result).toEqual({
|
||||
'1.0': 1,
|
||||
'2.0': 2,
|
||||
'3.0': 3,
|
||||
'1.0': '1.0',
|
||||
'2.0': '2.0',
|
||||
'3.0': '3.0',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -163,7 +224,7 @@ describe('Table utils', () => {
|
||||
name: 'value',
|
||||
type: FieldType.number,
|
||||
};
|
||||
const rows: any[] = [{ id: 0 }, { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }];
|
||||
const rows: any[] = [{ index: 0 }, { index: 1 }, { index: 2 }, { index: 3 }, { index: 4 }];
|
||||
|
||||
const result = calculateUniqueFieldValues(rows, field);
|
||||
|
||||
@@ -182,7 +243,7 @@ describe('Table utils', () => {
|
||||
name: 'value',
|
||||
type: FieldType.number,
|
||||
};
|
||||
const rows: any[] = [{ id: 0 }, { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }];
|
||||
const rows: any[] = [{ index: 0 }, { index: 1 }, { index: 2 }, { index: 3 }, { index: 4 }];
|
||||
|
||||
const result = calculateUniqueFieldValues(rows, field);
|
||||
|
||||
@@ -196,6 +257,59 @@ describe('Table utils', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('rowToFieldValue', () => {
|
||||
describe('happy paths', () => {
|
||||
describe('field without field display', () => {
|
||||
const field: any = { values: new ArrayVector(['a', 'b', 'c']) };
|
||||
const row = { index: 1 };
|
||||
|
||||
const result = rowToFieldValue(row, field);
|
||||
|
||||
expect(result).toEqual('b');
|
||||
});
|
||||
|
||||
describe('field with display processor', () => {
|
||||
const field: Field = {
|
||||
config: {},
|
||||
values: new ArrayVector([1, 2, 2, 1, 3, 5, 6]),
|
||||
name: 'value',
|
||||
type: FieldType.number,
|
||||
display: jest.fn((value: any) => ({
|
||||
numeric: 1,
|
||||
percent: 0.01,
|
||||
color: '',
|
||||
title: `${value}.0`,
|
||||
text: `${value}.0`,
|
||||
})),
|
||||
};
|
||||
const row = { index: 4 };
|
||||
|
||||
const result = rowToFieldValue(row, field);
|
||||
|
||||
expect(result).toEqual('3.0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('quick exist paths', () => {
|
||||
describe('field is missing', () => {
|
||||
const field = undefined;
|
||||
const row = { index: 0 };
|
||||
|
||||
const result = rowToFieldValue(row, field);
|
||||
|
||||
expect(result).toEqual('');
|
||||
});
|
||||
describe('row is missing', () => {
|
||||
const field: any = { values: new ArrayVector(['a', 'b', 'c']) };
|
||||
const row = undefined;
|
||||
|
||||
const result = rowToFieldValue(row, field);
|
||||
|
||||
expect(result).toEqual('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('valuesToOptions', () => {
|
||||
describe('when called with a record object', () => {
|
||||
it('then it should return sorted options from that object', () => {
|
||||
|
||||
@@ -78,7 +78,7 @@ export function getColumns(data: DataFrame, availableWidth: number, columnMinWid
|
||||
sortType: selectSortType(field.type),
|
||||
width: fieldTableOptions.width,
|
||||
minWidth: 50,
|
||||
filter: memoizeOne(filterByValue),
|
||||
filter: memoizeOne(filterByValue(field)),
|
||||
justifyContent: getTextAlign(field),
|
||||
});
|
||||
}
|
||||
@@ -116,23 +116,28 @@ function getCellComponent(displayMode: TableCellDisplayMode, field: Field) {
|
||||
return DefaultCell;
|
||||
}
|
||||
|
||||
export function filterByValue(rows: Row[], id: string, filterValues?: SelectableValue[]) {
|
||||
if (rows.length === 0) {
|
||||
return rows;
|
||||
}
|
||||
|
||||
if (!filterValues) {
|
||||
return rows;
|
||||
}
|
||||
|
||||
return rows.filter(row => {
|
||||
if (!row.values.hasOwnProperty(id)) {
|
||||
return false;
|
||||
export function filterByValue(field?: Field) {
|
||||
return function(rows: Row[], id: string, filterValues?: SelectableValue[]) {
|
||||
if (rows.length === 0) {
|
||||
return rows;
|
||||
}
|
||||
|
||||
const value = row.values[id];
|
||||
return filterValues.find(filter => filter.value === value) !== undefined;
|
||||
});
|
||||
if (!filterValues) {
|
||||
return rows;
|
||||
}
|
||||
|
||||
if (!field) {
|
||||
return rows;
|
||||
}
|
||||
|
||||
return rows.filter(row => {
|
||||
if (!row.values.hasOwnProperty(id)) {
|
||||
return false;
|
||||
}
|
||||
const value = rowToFieldValue(row, field);
|
||||
return filterValues.find(filter => filter.value === value) !== undefined;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function calculateUniqueFieldValues(rows: any[], field?: Field) {
|
||||
@@ -143,16 +148,25 @@ export function calculateUniqueFieldValues(rows: any[], field?: Field) {
|
||||
const set: Record<string, any> = {};
|
||||
|
||||
for (let index = 0; index < rows.length; index++) {
|
||||
const fieldIndex = parseInt(rows[index].id, 10);
|
||||
const fieldValue = field.values.get(fieldIndex);
|
||||
const displayValue = field.display ? field.display(fieldValue) : fieldValue;
|
||||
const value = field.display ? formattedValueToString(displayValue) : displayValue;
|
||||
set[value || '(Blanks)'] = fieldValue;
|
||||
const value = rowToFieldValue(rows[index], field);
|
||||
set[value || '(Blanks)'] = value;
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
export function rowToFieldValue(row: any, field?: Field): string {
|
||||
if (!field || !row) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const fieldValue = field.values.get(row.index);
|
||||
const displayValue = field.display ? field.display(fieldValue) : fieldValue;
|
||||
const value = field.display ? formattedValueToString(displayValue) : displayValue;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export function valuesToOptions(unique: Record<string, any>): SelectableValue[] {
|
||||
return Object.keys(unique)
|
||||
.reduce((all, key) => all.concat({ value: unique[key], label: key }), [] as SelectableValue[])
|
||||
|
||||
@@ -191,4 +191,28 @@ describe('ThresholdsEditor', () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('on load with invalid steps', () => {
|
||||
it('should exclude invalid steps and render a proper list', () => {
|
||||
const { instance } = setup({
|
||||
thresholds: {
|
||||
mode: ThresholdsMode.Absolute,
|
||||
steps: [
|
||||
{ value: -Infinity, color: '#7EB26D', key: 1 },
|
||||
{ value: 75, color: '#6ED0E0', key: 2 },
|
||||
{ color: '#7EB26D', key: 3 } as any,
|
||||
{ value: 78, color: '#EAB839', key: 4 },
|
||||
{ value: null, color: '#7EB26D', key: 5 } as any,
|
||||
{ value: null, color: '#7EB26D', key: 6 } as any,
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(getCurrentThresholds(instance).steps).toEqual([
|
||||
{ value: -Infinity, color: '#7EB26D' },
|
||||
{ value: 75, color: '#6ED0E0' },
|
||||
{ value: 78, color: '#EAB839' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,6 +18,7 @@ import { RadioButtonGroup } from '../Forms/RadioButtonGroup/RadioButtonGroup';
|
||||
import { Button } from '../Button';
|
||||
import { FullWidthButtonContainer } from '../Button/FullWidthButtonContainer';
|
||||
import { Label } from '../Forms/Label';
|
||||
import { isNumber } from 'lodash';
|
||||
|
||||
const modes: Array<SelectableValue<ThresholdsMode>> = [
|
||||
{ value: ThresholdsMode.Absolute, label: 'Absolute', description: 'Pick thresholds based on the absolute values' },
|
||||
@@ -38,6 +39,8 @@ interface State {
|
||||
}
|
||||
|
||||
export class ThresholdsEditor extends PureComponent<Props, State> {
|
||||
private latestThresholdInputRef: React.RefObject<HTMLInputElement>;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
@@ -45,6 +48,7 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
|
||||
steps[0].value = -Infinity;
|
||||
|
||||
this.state = { steps };
|
||||
this.latestThresholdInputRef = React.createRef();
|
||||
}
|
||||
|
||||
onAddThreshold = () => {
|
||||
@@ -67,7 +71,12 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
|
||||
const newThresholds = [...steps, add];
|
||||
sortThresholds(newThresholds);
|
||||
|
||||
this.setState({ steps: newThresholds }, this.onChange);
|
||||
this.setState({ steps: newThresholds }, () => {
|
||||
if (this.latestThresholdInputRef.current) {
|
||||
this.latestThresholdInputRef.current.focus();
|
||||
}
|
||||
this.onChange();
|
||||
});
|
||||
};
|
||||
|
||||
onRemoveThreshold = (threshold: ThresholdWithKey) => {
|
||||
@@ -136,7 +145,7 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
|
||||
});
|
||||
};
|
||||
|
||||
renderInput(threshold: ThresholdWithKey, styles: ThresholdStyles) {
|
||||
renderInput(threshold: ThresholdWithKey, styles: ThresholdStyles, idx: number) {
|
||||
const isPercent = this.props.thresholds.mode === ThresholdsMode.Percentage;
|
||||
|
||||
if (!isFinite(threshold.value)) {
|
||||
@@ -167,6 +176,7 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
|
||||
key={isPercent.toString()}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => this.onChangeThresholdValue(event, threshold)}
|
||||
value={threshold.value}
|
||||
ref={idx === 0 ? this.latestThresholdInputRef : null}
|
||||
onBlur={this.onBlur}
|
||||
prefix={
|
||||
<div className={styles.inputPrefix}>
|
||||
@@ -208,13 +218,11 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
|
||||
{steps
|
||||
.slice(0)
|
||||
.reverse()
|
||||
.map(threshold => {
|
||||
return (
|
||||
<div className={styles.item} key={`${threshold.key}`}>
|
||||
{this.renderInput(threshold, styles)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
.map((threshold, idx) => (
|
||||
<div className={styles.item} key={`${threshold.key}`}>
|
||||
{this.renderInput(threshold, styles, idx)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -242,13 +250,15 @@ function toThresholdsWithKey(steps?: Threshold[]): ThresholdWithKey[] {
|
||||
steps = [{ value: -Infinity, color: 'green' }];
|
||||
}
|
||||
|
||||
return steps.map(t => {
|
||||
return {
|
||||
color: t.color,
|
||||
value: t.value === null ? -Infinity : t.value,
|
||||
key: counter++,
|
||||
};
|
||||
});
|
||||
return steps
|
||||
.filter((t, i) => isNumber(t.value) || i === 0)
|
||||
.map(t => {
|
||||
return {
|
||||
color: t.color,
|
||||
value: t.value === null ? -Infinity : t.value,
|
||||
key: counter++,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function thresholdsWithoutKey(thresholds: ThresholdsConfig, steps: ThresholdWithKey[]): ThresholdsConfig {
|
||||
@@ -279,7 +289,6 @@ const getStyles = stylesFactory(
|
||||
wrapper: css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// margin-bottom: -${theme.spacing.formSpacingBase * 2}px;
|
||||
`,
|
||||
thresholds: css`
|
||||
display: flex;
|
||||
|
||||
@@ -6,6 +6,8 @@ import zip from 'lodash/zip';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import lightTheme from '../themes/light';
|
||||
import darkTheme from '../themes/dark';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { AlertVariant } from '../components/Alert/Alert';
|
||||
|
||||
export const PALETTE_ROWS = 4;
|
||||
export const PALETTE_COLUMNS = 14;
|
||||
@@ -97,7 +99,25 @@ function hslToHex(color: any) {
|
||||
|
||||
export function getTextColorForBackground(color: string) {
|
||||
const b = tinycolor(color).getBrightness();
|
||||
return b > 150 ? lightTheme.colors.textStrong : darkTheme.colors.textStrong;
|
||||
return b > 180 ? lightTheme.colors.textStrong : darkTheme.colors.textStrong;
|
||||
}
|
||||
|
||||
export let sortedColors = sortColorsByHue(colors);
|
||||
|
||||
/**
|
||||
* Returns colors used for severity color coding. Use for single color retrievel(0 index) or gradient definition
|
||||
* @internal
|
||||
**/
|
||||
export function getColorsFromSeverity(severity: AlertVariant, theme: GrafanaTheme): [string, string] {
|
||||
switch (severity) {
|
||||
case 'error':
|
||||
case 'warning':
|
||||
return [theme.palette.redBase, theme.palette.redShade];
|
||||
case 'info':
|
||||
return [theme.palette.blue80, theme.palette.blue77];
|
||||
case 'success':
|
||||
return [theme.palette.greenBase, theme.palette.greenShade];
|
||||
default:
|
||||
return [theme.palette.blue80, theme.palette.blue77];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jaegertracing/jaeger-ui-components",
|
||||
"version": "7.3.0-beta.2",
|
||||
"version": "7.3.5",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -14,8 +14,8 @@
|
||||
"typescript": "4.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grafana/data": "7.3.0-beta.2",
|
||||
"@grafana/ui": "7.3.0-beta.2",
|
||||
"@grafana/data": "7.3.5",
|
||||
"@grafana/ui": "7.3.5",
|
||||
"@types/classnames": "^2.2.7",
|
||||
"@types/deep-freeze": "^0.1.1",
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
|
||||
@@ -2,11 +2,8 @@
|
||||
|
||||
exports[`<ListView> shallow tests matches a snapshot 1`] = `
|
||||
<div
|
||||
onScroll={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"height": "100%",
|
||||
"overflowY": "auto",
|
||||
"position": "relative",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ describe('<ListView>', () => {
|
||||
itemsWrapperClassName: 'SomeClassName',
|
||||
viewBuffer: 10,
|
||||
viewBufferMin: 5,
|
||||
windowScroller: false,
|
||||
windowScroller: true,
|
||||
};
|
||||
|
||||
describe('shallow tests', () => {
|
||||
@@ -152,10 +152,6 @@ describe('<ListView>', () => {
|
||||
instance = wrapper.instance();
|
||||
});
|
||||
|
||||
it('getViewHeight() returns the viewHeight', () => {
|
||||
expect(instance.getViewHeight()).toBe(clientHeight);
|
||||
});
|
||||
|
||||
it('getBottomVisibleIndex() returns a number', () => {
|
||||
const n = instance.getBottomVisibleIndex();
|
||||
expect(Number.isNaN(n)).toBe(false);
|
||||
@@ -233,9 +229,9 @@ describe('<ListView>', () => {
|
||||
},
|
||||
});
|
||||
const hasChanged = instance._isViewChanged();
|
||||
expect(hasChanged).toBe(true);
|
||||
expect(spyFns.clientHeight).toHaveBeenCalled();
|
||||
expect(spyFns.scrollTop).toHaveBeenCalled();
|
||||
expect(hasChanged).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -67,7 +67,7 @@ type TListViewProps = {
|
||||
itemsWrapperClassName?: string;
|
||||
/**
|
||||
* When adding new items to the DOM, this is the number of items to add above
|
||||
* and below the current view. E.g. if list is 100 items and is srcolled
|
||||
* and below the current view. E.g. if list is 100 items and is scrolled
|
||||
* halfway down (so items [46, 55] are in view), then when a new range of
|
||||
* items is rendered, it will render items `46 - viewBuffer` to
|
||||
* `55 + viewBuffer`.
|
||||
@@ -89,15 +89,20 @@ type TListViewProps = {
|
||||
* - Ref:https://github.com/bvaughn/react-virtualized/blob/497e2a1942529560681d65a9ef9f5e9c9c9a49ba/docs/WindowScroller.md
|
||||
*/
|
||||
windowScroller?: boolean;
|
||||
/**
|
||||
* You need to pass in scrollElement when windowScroller is set to false.
|
||||
* This element is responsible for tracking scrolling for lazy loading.
|
||||
*/
|
||||
scrollElement?: Element;
|
||||
};
|
||||
|
||||
const DEFAULT_INITIAL_DRAW = 300;
|
||||
const DEFAULT_INITIAL_DRAW = 100;
|
||||
|
||||
/**
|
||||
* Virtualized list view component, for the most part, only renders the window
|
||||
* of items that are in-view with some buffer before and after. Listens for
|
||||
* scroll events and updates which items are rendered. See react-virtualized
|
||||
* for a suite of components with similar, but generalized, functinality.
|
||||
* for a suite of components with similar, but generalized, functionality.
|
||||
* https://github.com/bvaughn/react-virtualized
|
||||
*
|
||||
* Note: Presently, ListView cannot be a PureComponent. This is because ListView
|
||||
@@ -157,9 +162,9 @@ export default class ListView extends React.Component<TListViewProps> {
|
||||
_windowScrollListenerAdded: boolean;
|
||||
_htmlElm: HTMLElement;
|
||||
/**
|
||||
* HTMLElement holding the scroller.
|
||||
* Element holding the scroller.
|
||||
*/
|
||||
_wrapperElm: HTMLElement | TNil;
|
||||
_wrapperElm: Element | TNil;
|
||||
/**
|
||||
* HTMLElement holding the rendered items.
|
||||
*/
|
||||
@@ -202,6 +207,10 @@ export default class ListView extends React.Component<TListViewProps> {
|
||||
}
|
||||
window.addEventListener('scroll', this._onScroll);
|
||||
this._windowScrollListenerAdded = true;
|
||||
} else {
|
||||
// The wrapper element should be the one that handles the scrolling. Once we are not using scroll-canvas we can remove this.
|
||||
this._wrapperElm = this.props.scrollElement;
|
||||
this._wrapperElm?.addEventListener('scroll', this._onScroll);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,6 +223,8 @@ export default class ListView extends React.Component<TListViewProps> {
|
||||
componentWillUnmount() {
|
||||
if (this._windowScrollListenerAdded) {
|
||||
window.removeEventListener('scroll', this._onScroll);
|
||||
} else {
|
||||
this._wrapperElm?.removeEventListener('scroll', this._onScroll);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,8 +319,11 @@ export default class ListView extends React.Component<TListViewProps> {
|
||||
};
|
||||
|
||||
_initWrapper = (elm: HTMLElement | TNil) => {
|
||||
if (!this.props.windowScroller) {
|
||||
return;
|
||||
}
|
||||
this._wrapperElm = elm;
|
||||
if (!this.props.windowScroller && elm) {
|
||||
if (elm) {
|
||||
this._viewHeight = elm.clientHeight;
|
||||
}
|
||||
};
|
||||
@@ -374,8 +388,8 @@ export default class ListView extends React.Component<TListViewProps> {
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the height of the element at index `i`; first check the known heigths,
|
||||
* fallbck to `.props.itemHeightGetter(...)`.
|
||||
* Get the height of the element at index `i`; first check the known heights,
|
||||
* fallback to `.props.itemHeightGetter(...)`.
|
||||
*/
|
||||
_getHeight = (i: number) => {
|
||||
const key = this.props.getKeyFromIndex(i);
|
||||
|
||||
@@ -82,6 +82,7 @@ type TVirtualizedTraceViewOwnProps = {
|
||||
createSpanLink?: (
|
||||
span: TraceSpan
|
||||
) => { href: string; onClick?: (e: React.MouseEvent) => void; content: React.ReactNode };
|
||||
scrollElement?: Element;
|
||||
};
|
||||
|
||||
type VirtualizedTraceViewProps = TVirtualizedTraceViewOwnProps & TExtractUiFindFromStateReturn & TTraceTimeline;
|
||||
@@ -445,6 +446,7 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
||||
|
||||
render() {
|
||||
const styles = getStyles();
|
||||
const { scrollElement } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<ListView
|
||||
@@ -452,12 +454,13 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
||||
dataLength={this.rowStates.length}
|
||||
itemHeightGetter={this.getRowHeight}
|
||||
itemRenderer={this.renderRow}
|
||||
viewBuffer={300}
|
||||
viewBufferMin={100}
|
||||
viewBuffer={50}
|
||||
viewBufferMin={50}
|
||||
itemsWrapperClassName={styles.rowsWrapper}
|
||||
getKeyFromIndex={this.getKeyFromIndex}
|
||||
getIndexFromKey={this.getIndexFromKey}
|
||||
windowScroller
|
||||
windowScroller={false}
|
||||
scrollElement={scrollElement}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -102,6 +102,7 @@ type TProps = TExtractUiFindFromStateReturn & {
|
||||
createSpanLink?: (
|
||||
span: TraceSpan
|
||||
) => { href: string; onClick?: (e: React.MouseEvent) => void; content: React.ReactNode };
|
||||
scrollElement?: Element;
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
||||
@@ -17,7 +17,7 @@ import _isEqual from 'lodash/isEqual';
|
||||
// @ts-ignore
|
||||
import { getTraceSpanIdsAsTree } from '../selectors/trace';
|
||||
import { getConfigValue } from '../utils/config/get-config';
|
||||
import { TraceKeyValuePair, TraceSpan, TraceSpanData, Trace, TraceData } from '@grafana/data';
|
||||
import { TraceKeyValuePair, TraceSpan, Trace, TraceViewData } from '@grafana/data';
|
||||
// @ts-ignore
|
||||
import TreeNode from '../utils/TreeNode';
|
||||
|
||||
@@ -71,12 +71,11 @@ export function orderTags(spanTags: TraceKeyValuePair[], topPrefixes?: string[])
|
||||
* NOTE: Mutates `data` - Transform the HTTP response data into the form the app
|
||||
* generally requires.
|
||||
*/
|
||||
export default function transformTraceData(data: TraceData & { spans: TraceSpanData[] }): Trace | null {
|
||||
let { traceID } = data;
|
||||
if (!traceID) {
|
||||
export default function transformTraceData(data: TraceViewData | undefined): Trace | null {
|
||||
if (!data?.traceID) {
|
||||
return null;
|
||||
}
|
||||
traceID = traceID.toLowerCase();
|
||||
const traceID = data.traceID.toLowerCase();
|
||||
|
||||
let traceEndTime = 0;
|
||||
let traceStartTime = Number.MAX_SAFE_INTEGER;
|
||||
|
||||
@@ -256,6 +256,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
apiRoute.Get("/plugins/:pluginId/health", Wrap(hs.CheckHealth))
|
||||
apiRoute.Any("/plugins/:pluginId/resources", hs.CallResource)
|
||||
apiRoute.Any("/plugins/:pluginId/resources/*", hs.CallResource)
|
||||
apiRoute.Any("/plugins/errors", Wrap(hs.GetPluginErrorsList))
|
||||
|
||||
apiRoute.Group("/plugins", func(pluginRoute routing.RouteRegister) {
|
||||
pluginRoute.Get("/:pluginId/dashboards/", Wrap(GetPluginDashboards))
|
||||
|
||||
@@ -173,6 +173,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i
|
||||
"sort": getPanelSort(panel.Id),
|
||||
"skipDataQuery": panel.SkipDataQuery,
|
||||
"state": panel.State,
|
||||
"signature": panel.Signature,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,14 +230,17 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i
|
||||
"isEnterprise": hs.License.HasValidLicense(),
|
||||
},
|
||||
"licenseInfo": map[string]interface{}{
|
||||
"hasLicense": hs.License.HasLicense(),
|
||||
"expiry": hs.License.Expiry(),
|
||||
"stateInfo": hs.License.StateInfo(),
|
||||
"licenseUrl": hs.License.LicenseURL(c.SignedInUser),
|
||||
"hasLicense": hs.License.HasLicense(),
|
||||
"hasValidLicense": hs.License.HasValidLicense(),
|
||||
"expiry": hs.License.Expiry(),
|
||||
"stateInfo": hs.License.StateInfo(),
|
||||
"licenseUrl": hs.License.LicenseURL(c.SignedInUser),
|
||||
"edition": hs.License.Edition(),
|
||||
},
|
||||
"featureToggles": hs.Cfg.FeatureToggles,
|
||||
"rendererAvailable": hs.RenderService.IsAvailable(),
|
||||
"http2Enabled": hs.Cfg.Protocol == setting.HTTP2Scheme,
|
||||
"marketplaceUrl": hs.Cfg.MarketplaceURL,
|
||||
}
|
||||
|
||||
return jsonObj, nil
|
||||
|
||||
@@ -376,7 +376,7 @@ func (hs *HTTPServer) metricsEndpoint(ctx *macaron.Context) {
|
||||
}
|
||||
|
||||
promhttp.
|
||||
HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{}).
|
||||
HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{EnableOpenMetrics: true}).
|
||||
ServeHTTP(ctx.Resp, ctx.Req.Request)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package api
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
@@ -115,7 +114,7 @@ func (hs *HTTPServer) LoginView(c *models.ReqContext) {
|
||||
}
|
||||
}
|
||||
|
||||
if redirectTo, _ := url.QueryUnescape(c.GetCookie("redirect_to")); len(redirectTo) > 0 {
|
||||
if redirectTo := c.GetCookie("redirect_to"); len(redirectTo) > 0 {
|
||||
if err := hs.ValidateRedirectTo(redirectTo); err != nil {
|
||||
// the user is already logged so instead of rendering the login page with error
|
||||
// it should be redirected to the home page.
|
||||
@@ -161,7 +160,7 @@ func (hs *HTTPServer) LoginAPIPing(c *models.ReqContext) Response {
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) LoginPost(c *models.ReqContext, cmd dtos.LoginCommand) Response {
|
||||
action := "login"
|
||||
authModule := ""
|
||||
var user *models.User
|
||||
var response *NormalResponse
|
||||
|
||||
@@ -170,13 +169,13 @@ func (hs *HTTPServer) LoginPost(c *models.ReqContext, cmd dtos.LoginCommand) Res
|
||||
if err == nil && response.errMessage != "" {
|
||||
err = errors.New(response.errMessage)
|
||||
}
|
||||
hs.SendLoginLog(&models.SendLoginLogCommand{
|
||||
ReqContext: c,
|
||||
LogAction: action,
|
||||
User: user,
|
||||
HTTPStatus: response.status,
|
||||
Error: err,
|
||||
})
|
||||
hs.HooksService.RunLoginHook(&models.LoginInfo{
|
||||
AuthModule: authModule,
|
||||
User: user,
|
||||
LoginUsername: cmd.User,
|
||||
HTTPStatus: response.status,
|
||||
Error: err,
|
||||
}, c)
|
||||
}()
|
||||
|
||||
if setting.DisableLoginForm {
|
||||
@@ -192,9 +191,7 @@ func (hs *HTTPServer) LoginPost(c *models.ReqContext, cmd dtos.LoginCommand) Res
|
||||
}
|
||||
|
||||
err := bus.Dispatch(authQuery)
|
||||
if authQuery.AuthModule != "" {
|
||||
action += fmt.Sprintf("-%s", authQuery.AuthModule)
|
||||
}
|
||||
authModule = authQuery.AuthModule
|
||||
if err != nil {
|
||||
response = Error(401, "Invalid username or password", err)
|
||||
if err == login.ErrInvalidCredentials || err == login.ErrTooManyLoginAttempts || err == models.ErrUserNotFound {
|
||||
@@ -224,7 +221,7 @@ func (hs *HTTPServer) LoginPost(c *models.ReqContext, cmd dtos.LoginCommand) Res
|
||||
"message": "Logged in",
|
||||
}
|
||||
|
||||
if redirectTo, _ := url.QueryUnescape(c.GetCookie("redirect_to")); len(redirectTo) > 0 {
|
||||
if redirectTo := c.GetCookie("redirect_to"); len(redirectTo) > 0 {
|
||||
if err := hs.ValidateRedirectTo(redirectTo); err == nil {
|
||||
result["redirectUrl"] = redirectTo
|
||||
} else {
|
||||
@@ -316,11 +313,3 @@ func (hs *HTTPServer) RedirectResponseWithError(ctx *models.ReqContext, err erro
|
||||
|
||||
return Redirect(setting.AppSubUrl + "/login")
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) SendLoginLog(cmd *models.SendLoginLogCommand) {
|
||||
if err := bus.Dispatch(cmd); err != nil {
|
||||
if err != bus.ErrHandlerNotFound {
|
||||
hs.log.Warn("Error while sending login log", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,8 +38,8 @@ func GenStateString() (string, error) {
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
|
||||
loginInfo := LoginInformation{
|
||||
Action: "login-oauth",
|
||||
loginInfo := models.LoginInfo{
|
||||
AuthModule: "oauth",
|
||||
}
|
||||
if setting.OAuthService == nil {
|
||||
hs.handleOAuthLoginError(ctx, loginInfo, LoginError{
|
||||
@@ -50,7 +50,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
|
||||
}
|
||||
|
||||
name := ctx.Params(":name")
|
||||
loginInfo.Action += fmt.Sprintf("-%s", name)
|
||||
loginInfo.AuthModule = name
|
||||
connect, ok := social.SocialMap[name]
|
||||
if !ok {
|
||||
hs.handleOAuthLoginError(ctx, loginInfo, LoginError{
|
||||
@@ -172,8 +172,8 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
|
||||
return
|
||||
}
|
||||
|
||||
loginInfo.ExtUserInfo = buildExternalUserInfo(token, userInfo, name)
|
||||
loginInfo.User, err = syncUser(ctx, loginInfo.ExtUserInfo, connect)
|
||||
loginInfo.ExternalUser = *buildExternalUserInfo(token, userInfo, name)
|
||||
loginInfo.User, err = syncUser(ctx, &loginInfo.ExternalUser, connect)
|
||||
if err != nil {
|
||||
hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, err)
|
||||
return
|
||||
@@ -185,13 +185,8 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
|
||||
return
|
||||
}
|
||||
|
||||
hs.SendLoginLog(&models.SendLoginLogCommand{
|
||||
ReqContext: ctx,
|
||||
LogAction: loginInfo.Action,
|
||||
User: loginInfo.User,
|
||||
ExternalUser: loginInfo.ExtUserInfo,
|
||||
HTTPStatus: http.StatusOK,
|
||||
})
|
||||
loginInfo.HTTPStatus = http.StatusOK
|
||||
hs.HooksService.RunLoginHook(&loginInfo, ctx)
|
||||
metrics.MApiLoginOAuth.Inc()
|
||||
|
||||
if redirectTo, err := url.QueryUnescape(ctx.GetCookie("redirect_to")); err == nil && len(redirectTo) > 0 {
|
||||
@@ -280,36 +275,21 @@ type LoginError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
type LoginInformation struct {
|
||||
Action string
|
||||
User *models.User
|
||||
ExtUserInfo *models.ExternalUserInfo
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) handleOAuthLoginError(ctx *models.ReqContext, info LoginInformation, err LoginError) {
|
||||
func (hs *HTTPServer) handleOAuthLoginError(ctx *models.ReqContext, info models.LoginInfo, err LoginError) {
|
||||
ctx.Handle(err.HttpStatus, err.PublicMessage, err.Err)
|
||||
|
||||
logErr := err.Err
|
||||
if logErr == nil {
|
||||
logErr = errors.New(err.PublicMessage)
|
||||
info.Error = err.Err
|
||||
if info.Error == nil {
|
||||
info.Error = errors.New(err.PublicMessage)
|
||||
}
|
||||
info.HTTPStatus = err.HttpStatus
|
||||
|
||||
hs.SendLoginLog(&models.SendLoginLogCommand{
|
||||
ReqContext: ctx,
|
||||
LogAction: info.Action,
|
||||
HTTPStatus: err.HttpStatus,
|
||||
Error: logErr,
|
||||
})
|
||||
hs.HooksService.RunLoginHook(&info, ctx)
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) handleOAuthLoginErrorWithRedirect(ctx *models.ReqContext, info LoginInformation, err error, v ...interface{}) {
|
||||
func (hs *HTTPServer) handleOAuthLoginErrorWithRedirect(ctx *models.ReqContext, info models.LoginInfo, err error, v ...interface{}) {
|
||||
hs.redirectWithError(ctx, err, v...)
|
||||
|
||||
hs.SendLoginLog(&models.SendLoginLogCommand{
|
||||
ReqContext: ctx,
|
||||
LogAction: info.Action,
|
||||
User: info.User,
|
||||
ExternalUser: info.ExtUserInfo,
|
||||
Error: err,
|
||||
})
|
||||
info.Error = err
|
||||
hs.HooksService.RunLoginHook(&info, ctx)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -18,6 +17,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/login"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
"github.com/grafana/grafana/pkg/services/hooks"
|
||||
"github.com/grafana/grafana/pkg/services/licensing"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
@@ -318,6 +318,7 @@ func TestLoginPostRedirect(t *testing.T) {
|
||||
hs := &HTTPServer{
|
||||
log: &FakeLogger{},
|
||||
Cfg: setting.NewCfg(),
|
||||
HooksService: &hooks.HooksService{},
|
||||
License: &licensing.OSSLicensingService{},
|
||||
AuthTokenService: auth.NewFakeUserAuthTokenService(),
|
||||
}
|
||||
@@ -556,22 +557,23 @@ func setupAuthProxyLoginTest(enableLoginToken bool) *scenarioContext {
|
||||
return sc
|
||||
}
|
||||
|
||||
type loginLogTestReceiver struct {
|
||||
cmd *models.SendLoginLogCommand
|
||||
type loginHookTest struct {
|
||||
info *models.LoginInfo
|
||||
}
|
||||
|
||||
func (r *loginLogTestReceiver) SaveLoginLog(ctx context.Context, cmd *models.SendLoginLogCommand) error {
|
||||
r.cmd = cmd
|
||||
return nil
|
||||
func (r *loginHookTest) LoginHook(loginInfo *models.LoginInfo, req *models.ReqContext) {
|
||||
r.info = loginInfo
|
||||
}
|
||||
|
||||
func TestLoginPostSendLoginLog(t *testing.T) {
|
||||
func TestLoginPostRunLokingHook(t *testing.T) {
|
||||
sc := setupScenarioContext("/login")
|
||||
hookService := &hooks.HooksService{}
|
||||
hs := &HTTPServer{
|
||||
log: log.New("test"),
|
||||
Cfg: setting.NewCfg(),
|
||||
License: &licensing.OSSLicensingService{},
|
||||
AuthTokenService: auth.NewFakeUserAuthTokenService(),
|
||||
HooksService: hookService,
|
||||
}
|
||||
|
||||
sc.defaultHandler = Wrap(func(w http.ResponseWriter, c *models.ReqContext) Response {
|
||||
@@ -582,28 +584,26 @@ func TestLoginPostSendLoginLog(t *testing.T) {
|
||||
return hs.LoginPost(c, cmd)
|
||||
})
|
||||
|
||||
testReceiver := loginLogTestReceiver{}
|
||||
bus.AddHandlerCtx("login-log-receiver", testReceiver.SaveLoginLog)
|
||||
|
||||
type sendLoginLogCase struct {
|
||||
desc string
|
||||
authUser *models.User
|
||||
authModule string
|
||||
authErr error
|
||||
cmd models.SendLoginLogCommand
|
||||
}
|
||||
testHook := loginHookTest{}
|
||||
hookService.AddLoginHook(testHook.LoginHook)
|
||||
|
||||
testUser := &models.User{
|
||||
Id: 42,
|
||||
Email: "",
|
||||
}
|
||||
|
||||
testCases := []sendLoginLogCase{
|
||||
testCases := []struct {
|
||||
desc string
|
||||
authUser *models.User
|
||||
authModule string
|
||||
authErr error
|
||||
info models.LoginInfo
|
||||
}{
|
||||
{
|
||||
desc: "invalid credentials",
|
||||
authErr: login.ErrInvalidCredentials,
|
||||
cmd: models.SendLoginLogCommand{
|
||||
LogAction: "login",
|
||||
info: models.LoginInfo{
|
||||
AuthModule: "",
|
||||
HTTPStatus: 401,
|
||||
Error: login.ErrInvalidCredentials,
|
||||
},
|
||||
@@ -611,8 +611,8 @@ func TestLoginPostSendLoginLog(t *testing.T) {
|
||||
{
|
||||
desc: "user disabled",
|
||||
authErr: login.ErrUserDisabled,
|
||||
cmd: models.SendLoginLogCommand{
|
||||
LogAction: "login",
|
||||
info: models.LoginInfo{
|
||||
AuthModule: "",
|
||||
HTTPStatus: 401,
|
||||
Error: login.ErrUserDisabled,
|
||||
},
|
||||
@@ -621,8 +621,8 @@ func TestLoginPostSendLoginLog(t *testing.T) {
|
||||
desc: "valid Grafana user",
|
||||
authUser: testUser,
|
||||
authModule: "grafana",
|
||||
cmd: models.SendLoginLogCommand{
|
||||
LogAction: "login-grafana",
|
||||
info: models.LoginInfo{
|
||||
AuthModule: "grafana",
|
||||
User: testUser,
|
||||
HTTPStatus: 200,
|
||||
},
|
||||
@@ -631,8 +631,8 @@ func TestLoginPostSendLoginLog(t *testing.T) {
|
||||
desc: "valid LDAP user",
|
||||
authUser: testUser,
|
||||
authModule: "ldap",
|
||||
cmd: models.SendLoginLogCommand{
|
||||
LogAction: "login-ldap",
|
||||
info: models.LoginInfo{
|
||||
AuthModule: "ldap",
|
||||
User: testUser,
|
||||
HTTPStatus: 200,
|
||||
},
|
||||
@@ -650,14 +650,15 @@ func TestLoginPostSendLoginLog(t *testing.T) {
|
||||
sc.m.Post(sc.url, sc.defaultHandler)
|
||||
sc.fakeReqNoAssertions("POST", sc.url).exec()
|
||||
|
||||
cmd := testReceiver.cmd
|
||||
assert.Equal(t, c.cmd.LogAction, cmd.LogAction)
|
||||
assert.Equal(t, c.cmd.HTTPStatus, cmd.HTTPStatus)
|
||||
assert.Equal(t, c.cmd.Error, cmd.Error)
|
||||
info := testHook.info
|
||||
assert.Equal(t, c.info.AuthModule, info.AuthModule)
|
||||
assert.Equal(t, "admin", info.LoginUsername)
|
||||
assert.Equal(t, c.info.HTTPStatus, info.HTTPStatus)
|
||||
assert.Equal(t, c.info.Error, info.Error)
|
||||
|
||||
if c.cmd.User != nil {
|
||||
require.NotEmpty(t, cmd.User)
|
||||
assert.Equal(t, c.cmd.User.Id, cmd.User.Id)
|
||||
if c.info.User != nil {
|
||||
require.NotEmpty(t, info.User)
|
||||
assert.Equal(t, c.info.User.Id, info.User.Id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -102,13 +102,8 @@ func (proxy *DataSourceProxy) HandleRequest() {
|
||||
return
|
||||
}
|
||||
|
||||
proxyErrorLogger := logger.New("userId", proxy.ctx.UserId, "orgId", proxy.ctx.OrgId, "uname", proxy.ctx.Login, "path", proxy.ctx.Req.URL.Path, "remote_addr", proxy.ctx.RemoteAddr(), "referer", proxy.ctx.Req.Referer())
|
||||
|
||||
reverseProxy := &httputil.ReverseProxy{
|
||||
Director: proxy.getDirector(),
|
||||
FlushInterval: time.Millisecond * 200,
|
||||
ErrorLog: log.New(&logWrapper{logger: proxyErrorLogger}, "", 0),
|
||||
}
|
||||
proxyErrorLogger := logger.New("userId", proxy.ctx.UserId, "orgId", proxy.ctx.OrgId, "uname", proxy.ctx.Login,
|
||||
"path", proxy.ctx.Req.URL.Path, "remote_addr", proxy.ctx.RemoteAddr(), "referer", proxy.ctx.Req.Referer())
|
||||
|
||||
transport, err := proxy.ds.GetHttpTransport()
|
||||
if err != nil {
|
||||
@@ -116,16 +111,43 @@ func (proxy *DataSourceProxy) HandleRequest() {
|
||||
return
|
||||
}
|
||||
|
||||
reverseProxy.Transport = &handleResponseTransport{
|
||||
transport: transport,
|
||||
reverseProxy := &httputil.ReverseProxy{
|
||||
Director: proxy.director,
|
||||
FlushInterval: time.Millisecond * 200,
|
||||
ErrorLog: log.New(&logWrapper{logger: proxyErrorLogger}, "", 0),
|
||||
Transport: &handleResponseTransport{
|
||||
transport: transport,
|
||||
},
|
||||
ModifyResponse: func(resp *http.Response) error {
|
||||
if resp.StatusCode == 401 {
|
||||
// The data source rejected the request as unauthorized, convert to 400 (bad request)
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read data source response body: %w", err)
|
||||
}
|
||||
_ = resp.Body.Close()
|
||||
|
||||
proxyErrorLogger.Info("Authentication to data source failed", "body", string(body), "statusCode",
|
||||
resp.StatusCode)
|
||||
msg := "Authentication to data source failed"
|
||||
*resp = http.Response{
|
||||
StatusCode: 400,
|
||||
Status: "Bad Request",
|
||||
Body: ioutil.NopCloser(strings.NewReader(msg)),
|
||||
ContentLength: int64(len(msg)),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
proxy.logRequest()
|
||||
|
||||
span, ctx := opentracing.StartSpanFromContext(proxy.ctx.Req.Context(), "datasource reverse proxy")
|
||||
defer span.Finish()
|
||||
|
||||
proxy.ctx.Req.Request = proxy.ctx.Req.WithContext(ctx)
|
||||
|
||||
defer span.Finish()
|
||||
span.SetTag("datasource_id", proxy.ds.Id)
|
||||
span.SetTag("datasource_type", proxy.ds.Type)
|
||||
span.SetTag("user_id", proxy.ctx.SignedInUser.UserId)
|
||||
@@ -152,68 +174,64 @@ func (proxy *DataSourceProxy) addTraceFromHeaderValue(span opentracing.Span, hea
|
||||
}
|
||||
}
|
||||
|
||||
func (proxy *DataSourceProxy) getDirector() func(req *http.Request) {
|
||||
return func(req *http.Request) {
|
||||
req.URL.Scheme = proxy.targetUrl.Scheme
|
||||
req.URL.Host = proxy.targetUrl.Host
|
||||
req.Host = proxy.targetUrl.Host
|
||||
func (proxy *DataSourceProxy) director(req *http.Request) {
|
||||
req.URL.Scheme = proxy.targetUrl.Scheme
|
||||
req.URL.Host = proxy.targetUrl.Host
|
||||
req.Host = proxy.targetUrl.Host
|
||||
|
||||
reqQueryVals := req.URL.Query()
|
||||
reqQueryVals := req.URL.Query()
|
||||
|
||||
switch proxy.ds.Type {
|
||||
case models.DS_INFLUXDB_08:
|
||||
req.URL.Path = util.JoinURLFragments(proxy.targetUrl.Path, "db/"+proxy.ds.Database+"/"+proxy.proxyPath)
|
||||
reqQueryVals.Add("u", proxy.ds.User)
|
||||
reqQueryVals.Add("p", proxy.ds.DecryptedPassword())
|
||||
req.URL.RawQuery = reqQueryVals.Encode()
|
||||
case models.DS_INFLUXDB:
|
||||
req.URL.Path = util.JoinURLFragments(proxy.targetUrl.Path, proxy.proxyPath)
|
||||
req.URL.RawQuery = reqQueryVals.Encode()
|
||||
if !proxy.ds.BasicAuth {
|
||||
req.Header.Del("Authorization")
|
||||
req.Header.Add("Authorization", util.GetBasicAuthHeader(proxy.ds.User, proxy.ds.DecryptedPassword()))
|
||||
}
|
||||
default:
|
||||
req.URL.Path = util.JoinURLFragments(proxy.targetUrl.Path, proxy.proxyPath)
|
||||
switch proxy.ds.Type {
|
||||
case models.DS_INFLUXDB_08:
|
||||
req.URL.Path = util.JoinURLFragments(proxy.targetUrl.Path, "db/"+proxy.ds.Database+"/"+proxy.proxyPath)
|
||||
reqQueryVals.Add("u", proxy.ds.User)
|
||||
reqQueryVals.Add("p", proxy.ds.DecryptedPassword())
|
||||
req.URL.RawQuery = reqQueryVals.Encode()
|
||||
case models.DS_INFLUXDB:
|
||||
req.URL.Path = util.JoinURLFragments(proxy.targetUrl.Path, proxy.proxyPath)
|
||||
req.URL.RawQuery = reqQueryVals.Encode()
|
||||
if !proxy.ds.BasicAuth {
|
||||
req.Header.Set("Authorization", util.GetBasicAuthHeader(proxy.ds.User, proxy.ds.DecryptedPassword()))
|
||||
}
|
||||
default:
|
||||
req.URL.Path = util.JoinURLFragments(proxy.targetUrl.Path, proxy.proxyPath)
|
||||
}
|
||||
|
||||
if proxy.ds.BasicAuth {
|
||||
req.Header.Del("Authorization")
|
||||
req.Header.Add("Authorization", util.GetBasicAuthHeader(proxy.ds.BasicAuthUser, proxy.ds.DecryptedBasicAuthPassword()))
|
||||
if proxy.ds.BasicAuth {
|
||||
req.Header.Set("Authorization", util.GetBasicAuthHeader(proxy.ds.BasicAuthUser,
|
||||
proxy.ds.DecryptedBasicAuthPassword()))
|
||||
}
|
||||
|
||||
dsAuth := req.Header.Get("X-DS-Authorization")
|
||||
if len(dsAuth) > 0 {
|
||||
req.Header.Del("X-DS-Authorization")
|
||||
req.Header.Set("Authorization", dsAuth)
|
||||
}
|
||||
|
||||
applyUserHeader(proxy.cfg.SendUserHeader, req, proxy.ctx.SignedInUser)
|
||||
|
||||
keepCookieNames := []string{}
|
||||
if proxy.ds.JsonData != nil {
|
||||
if keepCookies := proxy.ds.JsonData.Get("keepCookies"); keepCookies != nil {
|
||||
keepCookieNames = keepCookies.MustStringArray()
|
||||
}
|
||||
}
|
||||
|
||||
dsAuth := req.Header.Get("X-DS-Authorization")
|
||||
if len(dsAuth) > 0 {
|
||||
req.Header.Del("X-DS-Authorization")
|
||||
req.Header.Del("Authorization")
|
||||
req.Header.Add("Authorization", dsAuth)
|
||||
}
|
||||
proxyutil.ClearCookieHeader(req, keepCookieNames)
|
||||
proxyutil.PrepareProxyRequest(req)
|
||||
|
||||
applyUserHeader(proxy.cfg.SendUserHeader, req, proxy.ctx.SignedInUser)
|
||||
req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion))
|
||||
|
||||
keepCookieNames := []string{}
|
||||
if proxy.ds.JsonData != nil {
|
||||
if keepCookies := proxy.ds.JsonData.Get("keepCookies"); keepCookies != nil {
|
||||
keepCookieNames = keepCookies.MustStringArray()
|
||||
}
|
||||
}
|
||||
// Clear Origin and Referer to avoir CORS issues
|
||||
req.Header.Del("Origin")
|
||||
req.Header.Del("Referer")
|
||||
|
||||
proxyutil.ClearCookieHeader(req, keepCookieNames)
|
||||
proxyutil.PrepareProxyRequest(req)
|
||||
if proxy.route != nil {
|
||||
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds)
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion))
|
||||
|
||||
// Clear Origin and Referer to avoir CORS issues
|
||||
req.Header.Del("Origin")
|
||||
req.Header.Del("Referer")
|
||||
|
||||
if proxy.route != nil {
|
||||
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds)
|
||||
}
|
||||
|
||||
if proxy.ds.JsonData != nil && proxy.ds.JsonData.Get("oauthPassThru").MustBool() {
|
||||
addOAuthPassThruAuth(proxy.ctx, req)
|
||||
}
|
||||
if proxy.ds.JsonData != nil && proxy.ds.JsonData.Get("oauthPassThru").MustBool() {
|
||||
addOAuthPassThruAuth(proxy.ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -276,7 +276,7 @@ func TestDSRouteRule(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
proxy.getDirector()(req)
|
||||
proxy.director(req)
|
||||
|
||||
Convey("Can translate request url and path", func() {
|
||||
So(req.URL.Host, ShouldEqual, "graphite:8080")
|
||||
@@ -303,7 +303,7 @@ func TestDSRouteRule(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
proxy.getDirector()(req)
|
||||
proxy.director(req)
|
||||
|
||||
Convey("Should add db to url", func() {
|
||||
So(req.URL.Path, ShouldEqual, "/db/site/")
|
||||
@@ -330,7 +330,7 @@ func TestDSRouteRule(t *testing.T) {
|
||||
cookies := "grafana_user=admin; grafana_remember=99; grafana_sess=11; JSESSION_ID=test"
|
||||
req.Header.Set("Cookie", cookies)
|
||||
|
||||
proxy.getDirector()(&req)
|
||||
proxy.director(&req)
|
||||
|
||||
Convey("Should clear all cookies", func() {
|
||||
So(req.Header.Get("Cookie"), ShouldEqual, "")
|
||||
@@ -357,7 +357,7 @@ func TestDSRouteRule(t *testing.T) {
|
||||
cookies := "grafana_user=admin; grafana_remember=99; grafana_sess=11; JSESSION_ID=test"
|
||||
req.Header.Set("Cookie", cookies)
|
||||
|
||||
proxy.getDirector()(&req)
|
||||
proxy.director(&req)
|
||||
|
||||
Convey("Should keep named cookies", func() {
|
||||
So(req.Header.Get("Cookie"), ShouldEqual, "JSESSION_ID=test")
|
||||
@@ -379,7 +379,7 @@ func TestDSRouteRule(t *testing.T) {
|
||||
req.Header.Add("X-Canary", "stillthere")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
proxy.getDirector()(req)
|
||||
proxy.director(req)
|
||||
|
||||
Convey("Should keep user request (including trailing slash)", func() {
|
||||
So(req.URL.String(), ShouldEqual, "http://host/root/path/to/folder/")
|
||||
@@ -436,7 +436,7 @@ func TestDSRouteRule(t *testing.T) {
|
||||
req, err = http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
proxy.getDirector()(req)
|
||||
proxy.director(req)
|
||||
|
||||
Convey("Should have access token in header", func() {
|
||||
So(req.Header.Get("Authorization"), ShouldEqual, fmt.Sprintf("%s %s", "Bearer", "testtoken"))
|
||||
@@ -518,7 +518,7 @@ func TestDSRouteRule(t *testing.T) {
|
||||
plugin := &plugins.DataSourcePlugin{}
|
||||
ds := &models.DataSource{Url: backend.URL, Type: models.DS_GRAPHITE}
|
||||
|
||||
responseRecorder := &CloseNotifierResponseRecorder{
|
||||
responseRecorder := &closeNotifierResponseRecorder{
|
||||
ResponseRecorder: httptest.NewRecorder(),
|
||||
}
|
||||
defer responseRecorder.Close()
|
||||
@@ -657,17 +657,17 @@ func TestNewDataSourceProxy_MSSQL(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type CloseNotifierResponseRecorder struct {
|
||||
type closeNotifierResponseRecorder struct {
|
||||
*httptest.ResponseRecorder
|
||||
closeChan chan bool
|
||||
}
|
||||
|
||||
func (r *CloseNotifierResponseRecorder) CloseNotify() <-chan bool {
|
||||
func (r *closeNotifierResponseRecorder) CloseNotify() <-chan bool {
|
||||
r.closeChan = make(chan bool)
|
||||
return r.closeChan
|
||||
}
|
||||
|
||||
func (r *CloseNotifierResponseRecorder) Close() {
|
||||
func (r *closeNotifierResponseRecorder) Close() {
|
||||
close(r.closeChan)
|
||||
}
|
||||
|
||||
@@ -685,7 +685,7 @@ func getDatasourceProxiedRequest(ctx *models.ReqContext, cfg *setting.Cfg) *http
|
||||
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
proxy.getDirector()(req)
|
||||
proxy.director(req)
|
||||
return req
|
||||
}
|
||||
|
||||
@@ -796,7 +796,7 @@ func runDatasourceAuthTest(test *Test) {
|
||||
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
proxy.getDirector()(req)
|
||||
proxy.director(req)
|
||||
|
||||
test.checkReq(req)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
@@ -121,7 +120,7 @@ func (hs *HTTPServer) GetPluginList(c *models.ReqContext) Response {
|
||||
listItem.DefaultNavUrl = setting.AppSubUrl + "/plugins/" + listItem.Id + "/"
|
||||
}
|
||||
|
||||
// filter out disabled
|
||||
// filter out disabled plugins
|
||||
if enabledFilter == "1" && !listItem.Enabled {
|
||||
continue
|
||||
}
|
||||
@@ -364,6 +363,10 @@ func (hs *HTTPServer) getCachedPluginSettings(pluginID string, user *models.Sign
|
||||
return query.Result, nil
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) GetPluginErrorsList(c *models.ReqContext) Response {
|
||||
return JSON(200, plugins.ScanningErrors())
|
||||
}
|
||||
|
||||
func translatePluginRequestErrorToAPIError(err error) Response {
|
||||
if errors.Is(err, backendplugin.ErrPluginNotRegistered) {
|
||||
return Error(404, "Plugin not found", err)
|
||||
|
||||
@@ -118,22 +118,24 @@ func (rr *routeRegister) Register(router Router) {
|
||||
|
||||
func (rr *routeRegister) route(pattern, method string, handlers ...macaron.Handler) {
|
||||
h := make([]macaron.Handler, 0)
|
||||
fullPattern := rr.prefix + pattern
|
||||
|
||||
for _, fn := range rr.namedMiddleware {
|
||||
h = append(h, fn(pattern))
|
||||
h = append(h, fn(fullPattern))
|
||||
}
|
||||
|
||||
h = append(h, rr.subfixHandlers...)
|
||||
h = append(h, handlers...)
|
||||
|
||||
for _, r := range rr.routes {
|
||||
if r.pattern == rr.prefix+pattern && r.method == method {
|
||||
if r.pattern == fullPattern && r.method == method {
|
||||
panic("cannot add duplicate route")
|
||||
}
|
||||
}
|
||||
|
||||
rr.routes = append(rr.routes, route{
|
||||
method: method,
|
||||
pattern: rr.prefix + pattern,
|
||||
pattern: fullPattern,
|
||||
handlers: h,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -214,8 +214,13 @@ func TestNamedMiddlewareRouteRegister(t *testing.T) {
|
||||
{method: "GET", pattern: "/user/admin/all", handlers: emptyHandlers(5)},
|
||||
}
|
||||
|
||||
namedMiddlewares := map[string]bool{}
|
||||
// Setup
|
||||
rr := NewRouteRegister(emptyHandler)
|
||||
rr := NewRouteRegister(func(name string) macaron.Handler {
|
||||
namedMiddlewares[name] = true
|
||||
|
||||
return struct{ name string }{name: name}
|
||||
})
|
||||
|
||||
rr.Delete("/admin", emptyHandler("1"))
|
||||
rr.Get("/down", emptyHandler("1"), emptyHandler("2"))
|
||||
@@ -247,6 +252,10 @@ func TestNamedMiddlewareRouteRegister(t *testing.T) {
|
||||
t.Errorf("want %s got %v", testTable[i].pattern, fr.route[i].pattern)
|
||||
}
|
||||
|
||||
if _, exist := namedMiddlewares[testTable[i].pattern]; !exist {
|
||||
t.Errorf("could not find named route named %s", testTable[i].pattern)
|
||||
}
|
||||
|
||||
if len(testTable[i].handlers) != len(fr.route[i].handlers) {
|
||||
t.Errorf("want %d handlers got %d handlers \ntestcase: %v\nroute: %v\n",
|
||||
len(testTable[i].handlers),
|
||||
|
||||
@@ -57,6 +57,11 @@ func (hs *HTTPServer) redirectFromShortURL(c *models.ReqContext) {
|
||||
return
|
||||
}
|
||||
|
||||
// Failure to update LastSeenAt should still allow to redirect
|
||||
if err := hs.ShortURLService.UpdateLastSeenAt(c.Req.Context(), shortURL); err != nil {
|
||||
hs.log.Error("Failed to update short URL last seen at", "error", err)
|
||||
}
|
||||
|
||||
hs.log.Debug("Redirecting short URL", "path", shortURL.Path)
|
||||
c.Redirect(setting.ToAbsUrl(shortURL.Path), 302)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
_ "github.com/russellhaering/goxmldsig"
|
||||
_ "github.com/stretchr/testify/require"
|
||||
_ "github.com/timberio/go-datemath"
|
||||
_ "golang.org/x/time/rate"
|
||||
_ "gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ type TracingService struct {
|
||||
customTags map[string]string
|
||||
samplerType string
|
||||
samplerParam float64
|
||||
samplingServerURL string
|
||||
log log.Logger
|
||||
closer io.Closer
|
||||
zipkinPropagation bool
|
||||
@@ -60,6 +61,7 @@ func (ts *TracingService) parseSettings() {
|
||||
ts.samplerParam = section.Key("sampler_param").MustFloat64(1)
|
||||
ts.zipkinPropagation = section.Key("zipkin_propagation").MustBool(false)
|
||||
ts.disableSharedZipkinSpans = section.Key("disable_shared_zipkin_spans").MustBool(false)
|
||||
ts.samplingServerURL = section.Key("sampling_server_url").MustString("")
|
||||
}
|
||||
|
||||
func (ts *TracingService) initJaegerCfg() (jaegercfg.Configuration, error) {
|
||||
@@ -67,8 +69,9 @@ func (ts *TracingService) initJaegerCfg() (jaegercfg.Configuration, error) {
|
||||
ServiceName: "grafana",
|
||||
Disabled: !ts.enabled,
|
||||
Sampler: &jaegercfg.SamplerConfig{
|
||||
Type: ts.samplerType,
|
||||
Param: ts.samplerParam,
|
||||
Type: ts.samplerType,
|
||||
Param: ts.samplerParam,
|
||||
SamplingServerURL: ts.samplingServerURL,
|
||||
},
|
||||
Reporter: &jaegercfg.ReporterConfig{
|
||||
LogSpans: false,
|
||||
|
||||
@@ -2,6 +2,7 @@ package middleware
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -53,18 +54,19 @@ func notAuthorized(c *models.ReqContext) {
|
||||
redirectTo = setting.AppSubUrl + c.Req.RequestURI
|
||||
}
|
||||
|
||||
// remove forceLogin query param if it exists
|
||||
if parsed, err := url.ParseRequestURI(redirectTo); err == nil {
|
||||
params := parsed.Query()
|
||||
params.Del("forceLogin")
|
||||
parsed.RawQuery = params.Encode()
|
||||
WriteCookie(c.Resp, "redirect_to", url.QueryEscape(parsed.String()), 0, newCookieOptions)
|
||||
} else {
|
||||
c.Logger.Debug("Failed parsing request URI; redirect cookie will not be set", "redirectTo", redirectTo, "error", err)
|
||||
}
|
||||
// remove any forceLogin=true params
|
||||
redirectTo = removeForceLoginParams(redirectTo)
|
||||
|
||||
WriteCookie(c.Resp, "redirect_to", url.QueryEscape(redirectTo), 0, newCookieOptions)
|
||||
c.Redirect(setting.AppSubUrl + "/login")
|
||||
}
|
||||
|
||||
var forceLoginParamsRegexp = regexp.MustCompile(`&?forceLogin=true`)
|
||||
|
||||
func removeForceLoginParams(str string) string {
|
||||
return forceLoginParamsRegexp.ReplaceAllString(str, "")
|
||||
}
|
||||
|
||||
func EnsureEditorOrViewerCanEdit(c *models.ReqContext) {
|
||||
if !c.SignedInUser.HasRole(models.ROLE_EDITOR) && !setting.ViewersCanEdit {
|
||||
accessForbidden(c)
|
||||
@@ -94,6 +96,14 @@ func Auth(options *AuthOptions) macaron.Handler {
|
||||
if err == nil {
|
||||
forceLogin = forceLoginParam
|
||||
}
|
||||
|
||||
if !forceLogin {
|
||||
orgIDValue := c.Req.URL.Query().Get("orgId")
|
||||
orgID, err := strconv.ParseInt(orgIDValue, 10, 64)
|
||||
if err == nil && orgID > 0 && orgID != c.OrgId {
|
||||
forceLogin = true
|
||||
}
|
||||
}
|
||||
}
|
||||
requireLogin := !c.AllowAnonymous || forceLogin
|
||||
if !c.IsSignedIn && options.ReqSignedIn && requireLogin {
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
@@ -32,6 +36,60 @@ func TestMiddlewareAuth(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Anonymous auth enabled", func() {
|
||||
origEnabled := setting.AnonymousEnabled
|
||||
t.Cleanup(func() {
|
||||
setting.AnonymousEnabled = origEnabled
|
||||
})
|
||||
origName := setting.AnonymousOrgName
|
||||
t.Cleanup(func() {
|
||||
setting.AnonymousOrgName = origName
|
||||
})
|
||||
setting.AnonymousEnabled = true
|
||||
setting.AnonymousOrgName = "test"
|
||||
|
||||
bus.AddHandler("test", func(query *models.GetOrgByNameQuery) error {
|
||||
query.Result = &models.Org{Id: 1, Name: "test"}
|
||||
return nil
|
||||
})
|
||||
|
||||
middlewareScenario(t, "ReqSignIn true and request with forceLogin in query string", func(sc *scenarioContext) {
|
||||
sc.m.Get("/secure", reqSignIn, sc.defaultHandler)
|
||||
|
||||
sc.fakeReq("GET", "/secure?forceLogin=true").exec()
|
||||
|
||||
Convey("Should redirect to login", func() {
|
||||
So(sc.resp.Code, ShouldEqual, 302)
|
||||
location, ok := sc.resp.Header()["Location"]
|
||||
So(ok, ShouldBeTrue)
|
||||
So(location[0], ShouldEqual, "/login")
|
||||
})
|
||||
})
|
||||
|
||||
middlewareScenario(t, "ReqSignIn true and request with same org provided in query string", func(sc *scenarioContext) {
|
||||
sc.m.Get("/secure", reqSignIn, sc.defaultHandler)
|
||||
|
||||
sc.fakeReq("GET", "/secure?orgId=1").exec()
|
||||
|
||||
Convey("Should not redirect to login", func() {
|
||||
So(sc.resp.Code, ShouldEqual, 200)
|
||||
})
|
||||
})
|
||||
|
||||
middlewareScenario(t, "ReqSignIn true and request with different org provided in query string", func(sc *scenarioContext) {
|
||||
sc.m.Get("/secure", reqSignIn, sc.defaultHandler)
|
||||
|
||||
sc.fakeReq("GET", "/secure?orgId=2").exec()
|
||||
|
||||
Convey("Should redirect to login", func() {
|
||||
So(sc.resp.Code, ShouldEqual, 302)
|
||||
location, ok := sc.resp.Header()["Location"]
|
||||
So(ok, ShouldBeTrue)
|
||||
So(location[0], ShouldEqual, "/login")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("snapshot public mode or signed in", func() {
|
||||
middlewareScenario(t, "Snapshot public mode disabled and unauthenticated request should return 401", func(sc *scenarioContext) {
|
||||
sc.m.Get("/api/snapshot", SnapshotPublicModeOrSignedIn(), sc.defaultHandler)
|
||||
@@ -48,3 +106,22 @@ func TestMiddlewareAuth(t *testing.T) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestRemoveForceLoginparams(t *testing.T) {
|
||||
tcs := []struct {
|
||||
inp string
|
||||
exp string
|
||||
}{
|
||||
{inp: "/?forceLogin=true", exp: "/?"},
|
||||
{inp: "/d/dash/dash-title?ordId=1&forceLogin=true", exp: "/d/dash/dash-title?ordId=1"},
|
||||
{inp: "/?kiosk&forceLogin=true", exp: "/?kiosk"},
|
||||
{inp: "/d/dash/dash-title?ordId=1&kiosk&forceLogin=true", exp: "/d/dash/dash-title?ordId=1&kiosk"},
|
||||
{inp: "/d/dash/dash-title?ordId=1&forceLogin=true&kiosk", exp: "/d/dash/dash-title?ordId=1&kiosk"},
|
||||
{inp: "/d/dash/dash-title?forceLogin=true&kiosk", exp: "/d/dash/dash-title?&kiosk"},
|
||||
}
|
||||
for i, tc := range tcs {
|
||||
t.Run(fmt.Sprintf("testcase %d", i), func(t *testing.T) {
|
||||
require.Equal(t, tc.exp, removeForceLoginParams(tc.inp))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
cw "github.com/weaveworks/common/middleware"
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
@@ -33,11 +34,11 @@ func Logger() macaron.Handler {
|
||||
rw := res.(macaron.ResponseWriter)
|
||||
c.Next()
|
||||
|
||||
timeTakenMs := time.Since(start) / time.Millisecond
|
||||
timeTaken := time.Since(start) / time.Millisecond
|
||||
|
||||
if timer, ok := c.Data["perfmon.timer"]; ok {
|
||||
timerTyped := timer.(prometheus.Summary)
|
||||
timerTyped.Observe(float64(timeTakenMs))
|
||||
timerTyped.Observe(float64(timeTaken))
|
||||
}
|
||||
|
||||
status := rw.Status()
|
||||
@@ -49,10 +50,25 @@ func Logger() macaron.Handler {
|
||||
|
||||
if ctx, ok := c.Data["ctx"]; ok {
|
||||
ctxTyped := ctx.(*models.ReqContext)
|
||||
if status == 500 {
|
||||
ctxTyped.Logger.Error("Request Completed", "method", req.Method, "path", req.URL.Path, "status", status, "remote_addr", c.RemoteAddr(), "time_ms", int64(timeTakenMs), "size", rw.Size(), "referer", req.Referer())
|
||||
logParams := []interface{}{
|
||||
"method", req.Method,
|
||||
"path", req.URL.Path,
|
||||
"status", status,
|
||||
"remote_addr", c.RemoteAddr(),
|
||||
"time_ms", int64(timeTaken),
|
||||
"size", rw.Size(),
|
||||
"referer", req.Referer(),
|
||||
}
|
||||
|
||||
traceID, exist := cw.ExtractTraceID(ctxTyped.Req.Request.Context())
|
||||
if exist {
|
||||
logParams = append(logParams, "traceID", traceID)
|
||||
}
|
||||
|
||||
if status >= 500 {
|
||||
ctxTyped.Logger.Error("Request Completed", logParams...)
|
||||
} else {
|
||||
ctxTyped.Logger.Info("Request Completed", "method", req.Method, "path", req.URL.Path, "status", status, "remote_addr", c.RemoteAddr(), "time_ms", int64(timeTakenMs), "size", rw.Size(), "referer", req.Referer())
|
||||
ctxTyped.Logger.Info("Request Completed", logParams...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
func OrgRedirect() macaron.Handler {
|
||||
return func(res http.ResponseWriter, req *http.Request, c *macaron.Context) {
|
||||
orgIdValue := req.URL.Query().Get("orgId")
|
||||
orgId, err := strconv.ParseInt(orgIdValue, 10, 32)
|
||||
orgId, err := strconv.ParseInt(orgIdValue, 10, 64)
|
||||
|
||||
if err != nil || orgId == 0 {
|
||||
return
|
||||
|
||||
@@ -9,12 +9,17 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
cw "github.com/weaveworks/common/middleware"
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
httpRequestsInFlight prometheus.Gauge
|
||||
httpRequestDurationHistogram *prometheus.HistogramVec
|
||||
|
||||
// DefBuckets are histogram buckets for the response time (in seconds)
|
||||
// of a network service, including one that is responding very slowly.
|
||||
defBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5}
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -30,9 +35,9 @@ func init() {
|
||||
Namespace: "grafana",
|
||||
Name: "http_request_duration_seconds",
|
||||
Help: "Histogram of latencies for HTTP requests.",
|
||||
Buckets: []float64{.1, .2, .4, 1, 3, 8, 20, 60, 120},
|
||||
Buckets: defBuckets,
|
||||
},
|
||||
[]string{"handler"},
|
||||
[]string{"handler", "status_code", "method"},
|
||||
)
|
||||
|
||||
prometheus.MustRegister(httpRequestsInFlight, httpRequestDurationHistogram)
|
||||
@@ -52,14 +57,26 @@ func RequestMetrics(cfg *setting.Cfg) func(handler string) macaron.Handler {
|
||||
|
||||
code := sanitizeCode(status)
|
||||
method := sanitizeMethod(req.Method)
|
||||
metrics.MHttpRequestTotal.WithLabelValues(handler, code, method).Inc()
|
||||
|
||||
duration := time.Since(now).Nanoseconds() / int64(time.Millisecond)
|
||||
|
||||
// enable histogram and disable summaries for http requests.
|
||||
// enable histogram and disable summaries + counters for http requests.
|
||||
if cfg.IsHTTPRequestHistogramEnabled() {
|
||||
httpRequestDurationHistogram.WithLabelValues(handler).Observe(float64(duration))
|
||||
// avoiding the sanitize functions for in the new instrumentation
|
||||
// since they dont make much sense. We should remove them later.
|
||||
histogram := httpRequestDurationHistogram.
|
||||
WithLabelValues(handler, strconv.Itoa(rw.Status()), req.Method)
|
||||
if traceID, ok := cw.ExtractSampledTraceID(c.Req.Context()); ok {
|
||||
// Need to type-convert the Observer to an
|
||||
// ExemplarObserver. This will always work for a
|
||||
// HistogramVec.
|
||||
histogram.(prometheus.ExemplarObserver).ObserveWithExemplar(
|
||||
time.Since(now).Seconds(), prometheus.Labels{"traceID": traceID},
|
||||
)
|
||||
return
|
||||
}
|
||||
histogram.Observe(time.Since(now).Seconds())
|
||||
} else {
|
||||
duration := time.Since(now).Nanoseconds() / int64(time.Millisecond)
|
||||
metrics.MHttpRequestTotal.WithLabelValues(handler, code, method).Inc()
|
||||
metrics.MHttpRequestSummary.WithLabelValues(handler, code, method).Observe(float64(duration))
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ var (
|
||||
ErrAlertNotificationStateVersionConflict = errors.New("alert notification state update version conflict")
|
||||
ErrAlertNotificationStateAlreadyExist = errors.New("alert notification state already exists")
|
||||
ErrAlertNotificationFailedGenerateUniqueUid = errors.New("Failed to generate unique alert notification uid")
|
||||
ErrAlertNotificationFailedTranslateUniqueID = errors.New("Failed to translate Notification Id to Uid")
|
||||
)
|
||||
|
||||
type AlertNotificationStateType string
|
||||
|
||||
@@ -123,6 +123,7 @@ var knownDatasourcePlugins = map[string]bool{
|
||||
"grafana-influxdb-flux-datasource": true,
|
||||
"doitintl-bigquery-datasource": true,
|
||||
"grafana-azure-data-explorer-datasource": true,
|
||||
"tempo": true,
|
||||
}
|
||||
|
||||
func IsKnownDataSourcePlugin(dsType string) bool {
|
||||
|
||||
@@ -193,13 +193,14 @@ func (ds *DataSource) sigV4Middleware(next http.RoundTripper) http.RoundTripper
|
||||
|
||||
return &SigV4Middleware{
|
||||
Config: &Config{
|
||||
AccessKey: decrypted["sigV4AccessKey"],
|
||||
SecretKey: decrypted["sigV4SecretKey"],
|
||||
Region: ds.JsonData.Get("sigV4Region").MustString(),
|
||||
AssumeRoleARN: ds.JsonData.Get("sigV4AssumeRoleArn").MustString(),
|
||||
AuthType: ds.JsonData.Get("sigV4AuthType").MustString(),
|
||||
ExternalID: ds.JsonData.Get("sigV4ExternalId").MustString(),
|
||||
Profile: ds.JsonData.Get("sigV4Profile").MustString(),
|
||||
DatasourceType: ds.Type,
|
||||
AccessKey: decrypted["sigV4AccessKey"],
|
||||
SecretKey: decrypted["sigV4SecretKey"],
|
||||
Region: ds.JsonData.Get("sigV4Region").MustString(),
|
||||
AssumeRoleARN: ds.JsonData.Get("sigV4AssumeRoleArn").MustString(),
|
||||
AuthType: ds.JsonData.Get("sigV4AuthType").MustString(),
|
||||
ExternalID: ds.JsonData.Get("sigV4ExternalId").MustString(),
|
||||
Profile: ds.JsonData.Get("sigV4Profile").MustString(),
|
||||
},
|
||||
Next: next,
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -17,3 +18,9 @@ type ShortUrl struct {
|
||||
CreatedAt int64
|
||||
LastSeenAt int64
|
||||
}
|
||||
|
||||
type DeleteShortUrlCommand struct {
|
||||
OlderThan time.Time
|
||||
|
||||
NumDeleted int64
|
||||
}
|
||||
|
||||
@@ -5,17 +5,15 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/defaults"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
v4 "github.com/aws/aws-sdk-go/aws/signer/v4"
|
||||
"github.com/aws/aws-sdk-go/private/protocol/rest"
|
||||
)
|
||||
|
||||
type AuthType string
|
||||
@@ -26,6 +24,17 @@ const (
|
||||
Credentials AuthType = "credentials"
|
||||
)
|
||||
|
||||
// Host header is likely not necessary here
|
||||
// (see https://github.com/golang/go/blob/cad6d1fef5147d31e94ee83934c8609d3ad150b7/src/net/http/request.go#L92)
|
||||
// but adding for completeness
|
||||
var permittedHeaders = map[string]struct{}{
|
||||
"Host": {},
|
||||
"Uber-Trace-Id": {},
|
||||
"User-Agent": {},
|
||||
"Accept": {},
|
||||
"Accept-Encoding": {},
|
||||
}
|
||||
|
||||
type SigV4Middleware struct {
|
||||
Config *Config
|
||||
Next http.RoundTripper
|
||||
@@ -36,6 +45,8 @@ type Config struct {
|
||||
|
||||
Profile string
|
||||
|
||||
DatasourceType string
|
||||
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
|
||||
@@ -63,21 +74,45 @@ func (m *SigV4Middleware) signRequest(req *http.Request) (http.Header, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if req.Body != nil {
|
||||
// consume entire request body so that the signer can generate a hash from the contents
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return signer.Sign(req, bytes.NewReader(body), "grafana", m.Config.Region, time.Now().UTC())
|
||||
body, err := replaceBody(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return signer.Sign(req, nil, "grafana", m.Config.Region, time.Now().UTC())
|
||||
|
||||
if strings.Contains(req.URL.RawPath, "%2C") {
|
||||
req.URL.RawPath = rest.EscapePath(req.URL.RawPath, false)
|
||||
}
|
||||
|
||||
stripHeaders(req)
|
||||
|
||||
return signer.Sign(req, bytes.NewReader(body), awsServiceNamespace(m.Config.DatasourceType), m.Config.Region, time.Now().UTC())
|
||||
}
|
||||
|
||||
func (m *SigV4Middleware) signer() (*v4.Signer, error) {
|
||||
c, err := m.credentials()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
authType := AuthType(m.Config.AuthType)
|
||||
|
||||
var c *credentials.Credentials
|
||||
switch authType {
|
||||
case Keys:
|
||||
c = credentials.NewStaticCredentials(m.Config.AccessKey, m.Config.SecretKey, "")
|
||||
case Credentials:
|
||||
c = credentials.NewSharedCredentials("", m.Config.Profile)
|
||||
case Default:
|
||||
// passing nil credentials will force AWS to allow a more complete credential chain vs the explicit default
|
||||
s, err := session.NewSession(&aws.Config{
|
||||
Region: aws.String(m.Config.Region),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if m.Config.AssumeRoleARN != "" {
|
||||
return v4.NewSigner(stscreds.NewCredentials(s, m.Config.AssumeRoleARN)), nil
|
||||
}
|
||||
|
||||
return v4.NewSigner(s.Config.Credentials), nil
|
||||
case "":
|
||||
return nil, fmt.Errorf("invalid SigV4 auth type")
|
||||
}
|
||||
|
||||
if m.Config.AssumeRoleARN != "" {
|
||||
@@ -94,17 +129,33 @@ func (m *SigV4Middleware) signer() (*v4.Signer, error) {
|
||||
return v4.NewSigner(c), nil
|
||||
}
|
||||
|
||||
func (m *SigV4Middleware) credentials() (*credentials.Credentials, error) {
|
||||
authType := AuthType(m.Config.AuthType)
|
||||
|
||||
switch authType {
|
||||
case Default:
|
||||
return defaults.CredChain(defaults.Config(), defaults.Handlers()), nil
|
||||
case Keys:
|
||||
return credentials.NewStaticCredentials(m.Config.AccessKey, m.Config.SecretKey, ""), nil
|
||||
case Credentials:
|
||||
return credentials.NewSharedCredentials("", m.Config.Profile), nil
|
||||
func replaceBody(req *http.Request) ([]byte, error) {
|
||||
if req.Body == nil {
|
||||
return []byte{}, nil
|
||||
}
|
||||
payload, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Body = ioutil.NopCloser(bytes.NewReader(payload))
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
func awsServiceNamespace(dsType string) string {
|
||||
switch dsType {
|
||||
case DS_ES:
|
||||
return "es"
|
||||
case DS_PROMETHEUS:
|
||||
return "aps"
|
||||
default:
|
||||
panic(fmt.Sprintf("Unsupported datasource %s", dsType))
|
||||
}
|
||||
}
|
||||
|
||||
func stripHeaders(req *http.Request) {
|
||||
for h := range req.Header {
|
||||
if _, exists := permittedHeaders[h]; !exists {
|
||||
req.Header.Del(h)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unrecognized authType: %s", authType)
|
||||
}
|
||||
|
||||
@@ -36,6 +36,15 @@ type ExternalUserInfo struct {
|
||||
IsDisabled bool
|
||||
}
|
||||
|
||||
type LoginInfo struct {
|
||||
AuthModule string
|
||||
User *User
|
||||
ExternalUser ExternalUserInfo
|
||||
LoginUsername string
|
||||
HTTPStatus int
|
||||
Error error
|
||||
}
|
||||
|
||||
// ---------------------
|
||||
// COMMANDS
|
||||
|
||||
@@ -65,15 +74,6 @@ type DeleteAuthInfoCommand struct {
|
||||
UserAuth *UserAuth
|
||||
}
|
||||
|
||||
type SendLoginLogCommand struct {
|
||||
ReqContext *ReqContext
|
||||
LogAction string
|
||||
User *User
|
||||
ExternalUser *ExternalUserInfo
|
||||
HTTPStatus int
|
||||
Error error
|
||||
}
|
||||
|
||||
// ----------------------
|
||||
// QUERIES
|
||||
|
||||
|
||||
14
pkg/plugins/error.go
Normal file
14
pkg/plugins/error.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package plugins
|
||||
|
||||
const (
|
||||
signatureMissing ErrorCode = "signatureMissing"
|
||||
signatureModified ErrorCode = "signatureModified"
|
||||
signatureInvalid ErrorCode = "signatureInvalid"
|
||||
)
|
||||
|
||||
type ErrorCode string
|
||||
|
||||
type PluginError struct {
|
||||
ErrorCode `json:"errorCode"`
|
||||
PluginID string `json:"pluginId,omitempty"`
|
||||
}
|
||||
@@ -96,7 +96,7 @@ func (pb *PluginBase) registerPlugin(base *PluginBase) error {
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(base.PluginDir, setting.StaticRootPath) {
|
||||
plog.Info("Registering plugin", "name", pb.Name)
|
||||
plog.Info("Registering plugin", "id", pb.Id)
|
||||
}
|
||||
|
||||
if len(pb.Dependencies.Plugins) == 0 {
|
||||
|
||||
@@ -37,16 +37,21 @@ var (
|
||||
GrafanaLatestVersion string
|
||||
GrafanaHasUpdate bool
|
||||
plog log.Logger
|
||||
|
||||
pluginScanningErrors map[string]*PluginError
|
||||
)
|
||||
|
||||
type unsignedPluginConditionFunc = func(plugin *PluginBase) bool
|
||||
|
||||
type PluginScanner struct {
|
||||
pluginPath string
|
||||
errors []error
|
||||
backendPluginManager backendplugin.Manager
|
||||
cfg *setting.Cfg
|
||||
requireSigned bool
|
||||
log log.Logger
|
||||
plugins map[string]*PluginBase
|
||||
pluginPath string
|
||||
errors []error
|
||||
backendPluginManager backendplugin.Manager
|
||||
cfg *setting.Cfg
|
||||
requireSigned bool
|
||||
log log.Logger
|
||||
plugins map[string]*PluginBase
|
||||
allowUnsignedPluginsCondition unsignedPluginConditionFunc
|
||||
}
|
||||
|
||||
type PluginManager struct {
|
||||
@@ -54,6 +59,10 @@ type PluginManager struct {
|
||||
Cfg *setting.Cfg `inject:""`
|
||||
log log.Logger
|
||||
scanningErrors []error
|
||||
|
||||
// AllowUnsignedPluginsCondition changes the policy for allowing unsigned plugins. Signature validation only runs when plugins are starting
|
||||
// and running plugins will not be terminated if they violate the new policy.
|
||||
AllowUnsignedPluginsCondition unsignedPluginConditionFunc
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -76,6 +85,7 @@ func (pm *PluginManager) Init() error {
|
||||
"renderer": RendererPlugin{},
|
||||
"transform": TransformPlugin{},
|
||||
}
|
||||
pluginScanningErrors = map[string]*PluginError{}
|
||||
|
||||
pm.log.Info("Starting plugin search")
|
||||
|
||||
@@ -185,12 +195,13 @@ func (pm *PluginManager) scanPluginPaths() error {
|
||||
// scan a directory for plugins.
|
||||
func (pm *PluginManager) scan(pluginDir string, requireSigned bool) error {
|
||||
scanner := &PluginScanner{
|
||||
pluginPath: pluginDir,
|
||||
backendPluginManager: pm.BackendPluginManager,
|
||||
cfg: pm.Cfg,
|
||||
requireSigned: requireSigned,
|
||||
log: pm.log,
|
||||
plugins: map[string]*PluginBase{},
|
||||
pluginPath: pluginDir,
|
||||
backendPluginManager: pm.BackendPluginManager,
|
||||
cfg: pm.Cfg,
|
||||
requireSigned: requireSigned,
|
||||
log: pm.log,
|
||||
plugins: map[string]*PluginBase{},
|
||||
allowUnsignedPluginsCondition: pm.AllowUnsignedPluginsCondition,
|
||||
}
|
||||
|
||||
// 1st pass: Scan plugins, also mapping plugins to their respective directories
|
||||
@@ -229,9 +240,11 @@ func (pm *PluginManager) scan(pluginDir string, requireSigned bool) error {
|
||||
}
|
||||
|
||||
pm.log.Debug("Found plugin", "id", plugin.Id, "signature", plugin.Signature, "hasRoot", plugin.Root != nil)
|
||||
if !scanner.validateSignature(plugin) {
|
||||
pm.log.Debug("Not adding plugin since it lacks a valid signature", "id", plugin.Id,
|
||||
"signature", plugin.Signature)
|
||||
signingError := scanner.validateSignature(plugin)
|
||||
if signingError != nil {
|
||||
pm.log.Debug("Failed to validate plugin signature. Will skip loading", "id", plugin.Id,
|
||||
"signature", plugin.Signature, "status", signingError.ErrorCode)
|
||||
pluginScanningErrors[plugin.Id] = signingError
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -242,8 +255,6 @@ func (pm *PluginManager) scan(pluginDir string, requireSigned bool) error {
|
||||
return fmt.Errorf("unknown plugin type %q", plugin.Type)
|
||||
}
|
||||
|
||||
loader := reflect.New(reflect.TypeOf(pluginGoType)).Interface().(PluginLoader)
|
||||
|
||||
jsonFPath := filepath.Join(plugin.PluginDir, "plugin.json")
|
||||
|
||||
// External plugins need a module.js file for SystemJS to load
|
||||
@@ -269,6 +280,8 @@ func (pm *PluginManager) scan(pluginDir string, requireSigned bool) error {
|
||||
|
||||
jsonParser := json.NewDecoder(reader)
|
||||
|
||||
loader := reflect.New(reflect.TypeOf(pluginGoType)).Interface().(PluginLoader)
|
||||
|
||||
// Load the full plugin, and add it to manager
|
||||
if err := loader.Load(jsonParser, plugin, scanner.backendPluginManager); err != nil {
|
||||
if errors.Is(err, duplicatePluginError{}) {
|
||||
@@ -276,10 +289,8 @@ func (pm *PluginManager) scan(pluginDir string, requireSigned bool) error {
|
||||
scanner.errors = append(scanner.errors, err)
|
||||
continue
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
pm.log.Debug("Successfully added plugin", "id", plugin.Id)
|
||||
}
|
||||
|
||||
@@ -370,16 +381,10 @@ func (scanner *PluginScanner) IsBackendOnlyPlugin(pluginType string) bool {
|
||||
}
|
||||
|
||||
// validateSignature validates a plugin's signature.
|
||||
func (s *PluginScanner) validateSignature(plugin *PluginBase) bool {
|
||||
// For the time being, we choose to only require back-end plugins to be signed
|
||||
// NOTE: the state is calculated again when setting metadata on the object
|
||||
if !plugin.Backend || !s.requireSigned {
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *PluginScanner) validateSignature(plugin *PluginBase) *PluginError {
|
||||
if plugin.Signature == PluginSignatureValid {
|
||||
s.log.Debug("Plugin has valid signature", "id", plugin.Id)
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
if plugin.Root != nil {
|
||||
@@ -393,7 +398,7 @@ func (s *PluginScanner) validateSignature(plugin *PluginBase) bool {
|
||||
plugin.Signature = plugin.Root.Signature
|
||||
if plugin.Signature == PluginSignatureValid {
|
||||
s.log.Debug("Plugin has valid signature (inherited from root)", "id", plugin.Id)
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -401,37 +406,70 @@ func (s *PluginScanner) validateSignature(plugin *PluginBase) bool {
|
||||
"state", plugin.Signature)
|
||||
}
|
||||
|
||||
// For the time being, we choose to only require back-end plugins to be signed
|
||||
// NOTE: the state is calculated again when setting metadata on the object
|
||||
if !plugin.Backend || !s.requireSigned {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch plugin.Signature {
|
||||
case PluginSignatureUnsigned:
|
||||
allowUnsigned := false
|
||||
for _, plug := range s.cfg.PluginsAllowUnsigned {
|
||||
if plug == plugin.Id {
|
||||
allowUnsigned = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if setting.Env != setting.Dev && !allowUnsigned {
|
||||
if allowed := s.allowUnsigned(plugin); !allowed {
|
||||
s.log.Debug("Plugin is unsigned", "id", plugin.Id)
|
||||
s.errors = append(s.errors, fmt.Errorf("plugin %q is unsigned", plugin.Id))
|
||||
return false
|
||||
return &PluginError{
|
||||
ErrorCode: signatureMissing,
|
||||
}
|
||||
}
|
||||
|
||||
s.log.Warn("Running an unsigned backend plugin", "pluginID", plugin.Id, "pluginDir",
|
||||
plugin.PluginDir)
|
||||
return true
|
||||
return nil
|
||||
case PluginSignatureInvalid:
|
||||
s.log.Debug("Plugin %q has an invalid signature", plugin.Id)
|
||||
s.errors = append(s.errors, fmt.Errorf("plugin %q has an invalid signature", plugin.Id))
|
||||
return false
|
||||
return &PluginError{
|
||||
ErrorCode: signatureInvalid,
|
||||
}
|
||||
case PluginSignatureModified:
|
||||
s.log.Debug("Plugin %q has a modified signature", plugin.Id)
|
||||
s.errors = append(s.errors, fmt.Errorf("plugin %q's signature has been modified", plugin.Id))
|
||||
return false
|
||||
return &PluginError{
|
||||
ErrorCode: signatureModified,
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("Plugin %q has unrecognized plugin signature state %q", plugin.Id, plugin.Signature))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PluginScanner) allowUnsigned(plugin *PluginBase) bool {
|
||||
if s.allowUnsignedPluginsCondition != nil {
|
||||
return s.allowUnsignedPluginsCondition(plugin)
|
||||
}
|
||||
|
||||
if setting.Env == setting.Dev {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, plug := range s.cfg.PluginsAllowUnsigned {
|
||||
if plug == plugin.Id {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func ScanningErrors() []PluginError {
|
||||
scanningErrs := make([]PluginError, 0)
|
||||
for id, e := range pluginScanningErrors {
|
||||
scanningErrs = append(scanningErrs, PluginError{
|
||||
ErrorCode: e.ErrorCode,
|
||||
PluginID: id,
|
||||
})
|
||||
}
|
||||
return scanningErrs
|
||||
}
|
||||
|
||||
func GetPluginMarkdown(pluginId string, name string) ([]byte, error) {
|
||||
plug, exists := Plugins[pluginId]
|
||||
if !exists {
|
||||
|
||||
@@ -273,7 +273,7 @@ func (s *Server) buildServiceGraph(services []*registry.Descriptor) error {
|
||||
objs := []interface{}{
|
||||
bus.GetBus(),
|
||||
s.cfg,
|
||||
routing.NewRouteRegister(middleware.RequestMetrics(s.cfg), middleware.RequestTracing),
|
||||
routing.NewRouteRegister(middleware.RequestTracing, middleware.RequestMetrics(s.cfg)),
|
||||
localcache.New(5*time.Minute, 10*time.Minute),
|
||||
s,
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package conditions
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/tsdb/prometheus"
|
||||
|
||||
gocontext "context"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
@@ -113,7 +116,7 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(getDsInfo); err != nil {
|
||||
return nil, fmt.Errorf("Could not find datasource %v", err)
|
||||
return nil, fmt.Errorf("could not find datasource: %w", err)
|
||||
}
|
||||
|
||||
req := c.getRequestForAlertRule(getDsInfo.Result, timeRange, context.IsDebug)
|
||||
@@ -158,11 +161,7 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("tsdb.HandleRequest() error %v", err)
|
||||
return nil, toCustomError(err)
|
||||
}
|
||||
|
||||
for _, v := range resp.Results {
|
||||
@@ -334,7 +333,9 @@ func FrameToSeriesSlice(frame *data.Frame) (tsdb.TimeSeriesSlice, error) {
|
||||
switch {
|
||||
case field.Config != nil && field.Config.DisplayName != "":
|
||||
ts.Name = field.Config.DisplayName
|
||||
case field.Labels != nil:
|
||||
case field.Config != nil && field.Config.DisplayNameFromDS != "":
|
||||
ts.Name = field.Config.DisplayNameFromDS
|
||||
case len(field.Labels) > 0:
|
||||
ts.Tags = field.Labels.Copy()
|
||||
// Tags are appended to the name so they are eventually included in EvalMatch's Metric property
|
||||
// for display in notifications.
|
||||
@@ -359,3 +360,18 @@ func FrameToSeriesSlice(frame *data.Frame) (tsdb.TimeSeriesSlice, error) {
|
||||
|
||||
return seriesSlice, nil
|
||||
}
|
||||
|
||||
func toCustomError(err error) error {
|
||||
// is context timeout
|
||||
if errors.Is(err, gocontext.DeadlineExceeded) {
|
||||
return fmt.Errorf("alert execution exceeded the timeout")
|
||||
}
|
||||
|
||||
// is Prometheus error
|
||||
if prometheus.IsAPIError(err) {
|
||||
return prometheus.ConvertAPIError(err)
|
||||
}
|
||||
|
||||
// generic fallback
|
||||
return fmt.Errorf("tsdb.HandleRequest() error %v", err)
|
||||
}
|
||||
|
||||
@@ -296,6 +296,53 @@ func TestFrameToSeriesSlice(t *testing.T) {
|
||||
},
|
||||
Err: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "empty labels",
|
||||
frame: data.NewFrame("",
|
||||
data.NewField("Time", data.Labels{}, []time.Time{}),
|
||||
data.NewField(`Values`, data.Labels{}, []float64{})),
|
||||
|
||||
seriesSlice: tsdb.TimeSeriesSlice{
|
||||
&tsdb.TimeSeries{
|
||||
Name: "Values",
|
||||
Points: tsdb.TimeSeriesPoints{},
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "display name from data source",
|
||||
frame: data.NewFrame("",
|
||||
data.NewField("Time", data.Labels{}, []time.Time{}),
|
||||
data.NewField(`Values`, data.Labels{}, []*int64{}).SetConfig(&data.FieldConfig{
|
||||
DisplayNameFromDS: "sloth",
|
||||
})),
|
||||
|
||||
seriesSlice: tsdb.TimeSeriesSlice{
|
||||
&tsdb.TimeSeries{
|
||||
Name: "sloth",
|
||||
Points: tsdb.TimeSeriesPoints{},
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "prefer display name over data source display name",
|
||||
frame: data.NewFrame("",
|
||||
data.NewField("Time", data.Labels{}, []time.Time{}),
|
||||
data.NewField(`Values`, data.Labels{}, []*int64{}).SetConfig(&data.FieldConfig{
|
||||
DisplayName: "sloth #1",
|
||||
DisplayNameFromDS: "sloth #2",
|
||||
})),
|
||||
|
||||
seriesSlice: tsdb.TimeSeriesSlice{
|
||||
&tsdb.TimeSeries{
|
||||
Name: "sloth #1",
|
||||
Points: tsdb.TimeSeriesPoints{},
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@@ -74,7 +74,7 @@ func copyJSON(in json.Marshaler) (*simplejson.Json, error) {
|
||||
return simplejson.NewJson(rawJSON)
|
||||
}
|
||||
|
||||
func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json, validateAlertFunc func(*models.Alert) bool) ([]*models.Alert, error) {
|
||||
func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json, validateAlertFunc func(*models.Alert) bool, logTranslationFailures bool) ([]*models.Alert, error) {
|
||||
alerts := make([]*models.Alert, 0)
|
||||
|
||||
for _, panelObj := range jsonWithPanels.Get("panels").MustArray() {
|
||||
@@ -84,7 +84,7 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json,
|
||||
// check if the panel is collapsed
|
||||
if collapsed && collapsedJSON.MustBool() {
|
||||
// extract alerts from sub panels for collapsed panels
|
||||
alertSlice, err := e.getAlertFromPanels(panel, validateAlertFunc)
|
||||
alertSlice, err := e.getAlertFromPanels(panel, validateAlertFunc, logTranslationFailures)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -188,7 +188,7 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json,
|
||||
alert.Settings = jsonAlert
|
||||
|
||||
// validate
|
||||
_, err = NewRuleFromDBAlert(alert)
|
||||
_, err = NewRuleFromDBAlert(alert, logTranslationFailures)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -209,10 +209,10 @@ func validateAlertRule(alert *models.Alert) bool {
|
||||
|
||||
// GetAlerts extracts alerts from the dashboard json and does full validation on the alert json data.
|
||||
func (e *DashAlertExtractor) GetAlerts() ([]*models.Alert, error) {
|
||||
return e.extractAlerts(validateAlertRule)
|
||||
return e.extractAlerts(validateAlertRule, true)
|
||||
}
|
||||
|
||||
func (e *DashAlertExtractor) extractAlerts(validateFunc func(alert *models.Alert) bool) ([]*models.Alert, error) {
|
||||
func (e *DashAlertExtractor) extractAlerts(validateFunc func(alert *models.Alert) bool, logTranslationFailures bool) ([]*models.Alert, error) {
|
||||
dashboardJSON, err := copyJSON(e.Dash.Data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -226,7 +226,7 @@ func (e *DashAlertExtractor) extractAlerts(validateFunc func(alert *models.Alert
|
||||
if len(rows) > 0 {
|
||||
for _, rowObj := range rows {
|
||||
row := simplejson.NewFromAny(rowObj)
|
||||
a, err := e.getAlertFromPanels(row, validateFunc)
|
||||
a, err := e.getAlertFromPanels(row, validateFunc, logTranslationFailures)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -234,7 +234,7 @@ func (e *DashAlertExtractor) extractAlerts(validateFunc func(alert *models.Alert
|
||||
alerts = append(alerts, a...)
|
||||
}
|
||||
} else {
|
||||
a, err := e.getAlertFromPanels(dashboardJSON, validateFunc)
|
||||
a, err := e.getAlertFromPanels(dashboardJSON, validateFunc, logTranslationFailures)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -249,6 +249,6 @@ func (e *DashAlertExtractor) extractAlerts(validateFunc func(alert *models.Alert
|
||||
// ValidateAlerts validates alerts in the dashboard json but does not require a valid dashboard id
|
||||
// in the first validation pass.
|
||||
func (e *DashAlertExtractor) ValidateAlerts() error {
|
||||
_, err := e.extractAlerts(func(alert *models.Alert) bool { return alert.OrgId != 0 && alert.PanelId != 0 })
|
||||
_, err := e.extractAlerts(func(alert *models.Alert) bool { return alert.OrgId != 0 && alert.PanelId != 0 }, false)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -119,14 +119,14 @@ func (dd *DingDingNotifier) genBody(evalContext *alerting.EvalContext, messageUR
|
||||
}
|
||||
|
||||
for i, match := range evalContext.EvalMatches {
|
||||
message += fmt.Sprintf("\\n%2d. %s: %s", i+1, match.Metric, match.Value)
|
||||
message += fmt.Sprintf("\n%2d. %s: %s", i+1, match.Metric, match.Value)
|
||||
}
|
||||
|
||||
var bodyMsg map[string]interface{}
|
||||
if dd.MsgType == "actionCard" {
|
||||
// Embed the pic into the markdown directly because actionCard doesn't have a picUrl field
|
||||
if dd.NeedsImage() && picURL != "" {
|
||||
message = "\\n\\n" + message
|
||||
message = "\n\n" + message
|
||||
}
|
||||
|
||||
bodyMsg = map[string]interface{}{
|
||||
|
||||
@@ -36,7 +36,7 @@ func (arr *defaultRuleReader) fetch() []*Rule {
|
||||
|
||||
res := make([]*Rule, 0)
|
||||
for _, ruleDef := range cmd.Result {
|
||||
if model, err := NewRuleFromDBAlert(ruleDef); err != nil {
|
||||
if model, err := NewRuleFromDBAlert(ruleDef, false); err != nil {
|
||||
arr.log.Error("Could not build alert model for rule", "ruleId", ruleDef.Id, "error", err)
|
||||
} else {
|
||||
res = append(res, model)
|
||||
|
||||
@@ -113,7 +113,7 @@ func getTimeDurationStringToSeconds(str string) (int64, error) {
|
||||
|
||||
// NewRuleFromDBAlert maps a db version of
|
||||
// alert to an in-memory version.
|
||||
func NewRuleFromDBAlert(ruleDef *models.Alert) (*Rule, error) {
|
||||
func NewRuleFromDBAlert(ruleDef *models.Alert, logTranslationFailures bool) (*Rule, error) {
|
||||
model := &Rule{}
|
||||
model.ID = ruleDef.Id
|
||||
model.OrgID = ruleDef.OrgId
|
||||
@@ -140,7 +140,13 @@ func NewRuleFromDBAlert(ruleDef *models.Alert) (*Rule, error) {
|
||||
if id, err := jsonModel.Get("id").Int64(); err == nil {
|
||||
uid, err := translateNotificationIDToUID(id, ruleDef.OrgId)
|
||||
if err != nil {
|
||||
logger.Error("Unable to translate notification id to uid", "error", err.Error(), "dashboardId", model.DashboardID, "alertId", model.ID, "panelId", model.PanelID, "notificationId", id)
|
||||
if !errors.Is(err, models.ErrAlertNotificationFailedTranslateUniqueID) {
|
||||
logger.Error("Failed to tranlate notification id to uid", "error", err.Error(), "dashboardId", model.DashboardID, "alert", model.Name, "panelId", model.PanelID, "notificationId", id)
|
||||
}
|
||||
|
||||
if logTranslationFailures {
|
||||
logger.Warn("Unable to translate notification id to uid", "dashboardId", model.DashboardID, "alert", model.Name, "panelId", model.PanelID, "notificationId", id)
|
||||
}
|
||||
} else {
|
||||
model.Notifications = append(model.Notifications, uid)
|
||||
}
|
||||
@@ -176,7 +182,6 @@ func NewRuleFromDBAlert(ruleDef *models.Alert) (*Rule, error) {
|
||||
func translateNotificationIDToUID(id int64, orgID int64) (string, error) {
|
||||
notificationUID, err := getAlertNotificationUIDByIDAndOrgID(id, orgID)
|
||||
if err != nil {
|
||||
logger.Debug("Failed to translate Notification Id to Uid", "orgID", orgID, "Id", id)
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ func TestAlertRuleModel(t *testing.T) {
|
||||
Settings: alertJSON,
|
||||
}
|
||||
|
||||
alertRule, err := NewRuleFromDBAlert(alert)
|
||||
alertRule, err := NewRuleFromDBAlert(alert, false)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(alertRule.Conditions), ShouldEqual, 1)
|
||||
@@ -142,7 +142,7 @@ func TestAlertRuleModel(t *testing.T) {
|
||||
Settings: alertJSON,
|
||||
}
|
||||
|
||||
alertRule, err := NewRuleFromDBAlert(alert)
|
||||
alertRule, err := NewRuleFromDBAlert(alert, false)
|
||||
Convey("swallows the error", func() {
|
||||
So(err, ShouldBeNil)
|
||||
So(alertRule.Notifications, ShouldNotContain, "999")
|
||||
@@ -175,7 +175,7 @@ func TestAlertRuleModel(t *testing.T) {
|
||||
Settings: alertJSON,
|
||||
}
|
||||
|
||||
alertRule, err := NewRuleFromDBAlert(alert)
|
||||
alertRule, err := NewRuleFromDBAlert(alert, false)
|
||||
So(err, ShouldBeNil)
|
||||
So(alertRule.Frequency, ShouldEqual, 60)
|
||||
})
|
||||
@@ -213,7 +213,7 @@ func TestAlertRuleModel(t *testing.T) {
|
||||
Settings: alertJSON,
|
||||
}
|
||||
|
||||
_, err := NewRuleFromDBAlert(alert)
|
||||
_, err := NewRuleFromDBAlert(alert, false)
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err.Error(), ShouldEqual, "alert validation error: Neither id nor uid is specified in 'notifications' block, type assertion to string failed AlertId: 1 PanelId: 1 DashboardId: 1")
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user