Compare commits

...

15 Commits

Author SHA1 Message Date
Grot (@grafanabot)
147704deb9 "Release: Updated versions in package to 8.1.0-beta.2" (#37138) 2021-07-23 10:29:33 +02:00
Grot (@grafanabot)
078d716be9 IconButton: Put tooltip text as aria-label (#36760) (#37137)
* Make tooltip prop aria-label

* Add ariaLabel prop

(cherry picked from commit 8af83b8b78)

Co-authored-by: Tobias Skarhed <1438972+tskarhed@users.noreply.github.com>
2021-07-23 10:12:16 +02:00
Grot (@grafanabot)
04cb471599 Expand the value string in annotations and labels of alerts (#37051) (#37105)
This commit makes it possible to use the value string in
annotations and labels for alerts with "{{ $value }}"

(cherry picked from commit 2f4c893cf3)

Co-authored-by: George Robinson <85952834+gerobinson@users.noreply.github.com>
2021-07-23 09:28:49 +02:00
Grot (@grafanabot)
6564f22772 Gazetteer: Update Countries Json (#37129) (#37132)
(cherry picked from commit 2b51e94537)

Co-authored-by: Bryan Uribe <buribe@hmc.edu>
2021-07-23 09:25:20 +02:00
Grot (@grafanabot)
13cd3ea28b Explore: Fix encoding of internal URLs (#36919) (#37100)
* Encode internal explore url

* Fix tests

* Fix comma

(cherry picked from commit 93b4cc7035)

Co-authored-by: Andrej Ocenas <mr.ocenas@gmail.com>
2021-07-23 09:19:25 +02:00
Grot (@grafanabot)
d5e0665081 Storybook: Add a11y addon (#36790) (#37110)
* Storybook: Add a11y addon

* Update lockfile

* Bump Storybook addon versions

* Put Icon at top

* addon-knobs 6.3.0

(cherry picked from commit 437424d5d6)

Co-authored-by: Tobias Skarhed <1438972+tskarhed@users.noreply.github.com>
2021-07-23 09:17:59 +02:00
Grot (@grafanabot)
909141592d StatPanel: Disable selection on Sparkline (#37125) (#37128)
(cherry picked from commit a1bbe797df)

Co-authored-by: nikki-kiga <42276368+nikki-kiga@users.noreply.github.com>
2021-07-23 09:14:51 +02:00
Grot (@grafanabot)
fda235a862 Infra: Azure authentication in HttpClientProvider (#36932) (#37124)
* Azure middleware in HttpClientProxy

* Azure authentication under feature flag

* Minor fixes

* Add prefixes to not clash with JsonData

* Return error if JsonData cannot be parsed

* Return original string if URL invalid

* Tests for datasource_cache

(cherry picked from commit c1963024ec)

Co-authored-by: Sergey Kostrukov <sergey@kostrukov.com>
2021-07-22 23:12:31 +02:00
Grot (@grafanabot)
154231a58d Doc: first draft of 8.1 what's new (#37021) (#37115)
* Create whats-new-in-v8-1.md

* Updated index page.

* Fixed two relrefs.

* Added section for annotation panel

(cherry picked from commit 013218e075)

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
2021-07-22 15:28:48 -04:00
Grot (@grafanabot)
197e4344da Prometheus: Azure authentication in configuration UI (#35860) (#37116)
* Azure authentication settings

* Persisting credentials

* Azure settings

* Prometheus-specific settings component

* Azure Prometheus Resource ID configuration

* DataSourceHttpSettings with extensibility for Azure

* Feature toggle for Azure auth

* Fix snapshot

* Update format of persisted credentials

* AzureSettings renamed to AzureAuthSettings

(cherry picked from commit 4664cba935)

Co-authored-by: Sergey Kostrukov <sergey@kostrukov.com>
2021-07-22 21:22:31 +02:00
Grot (@grafanabot)
64b008e28b ReleaseNotes: Updated changelog and release notes for 8.1.0-beta1 (#37111) (#37112) 2021-07-22 18:38:49 +02:00
Grot (@grafanabot)
bc9ac1199b fix sample.ini (#37106) (#37107)
(cherry picked from commit 6b2d33dc14)

Co-authored-by: Alexander Emelin <frvzmb@gmail.com>
2021-07-22 17:25:47 +02:00
Grot (@grafanabot)
2b15e1a962 Folder API: optionally force deleting Grafana 8 alerts when deleting a folder (or error) (#36427) (#37094)
* Folder API: Add an optional query parameter for allowing deleting a  folder containing rules

* Update frontend

- Set forceDeleteRules=true when frontend deletes a folder
- Improve modal text

* Update docs

* Apply suggestions from code review

Co-authored-by: gotjosh <josue@grafana.com>
Co-authored-by: Nathan Rodman <nathanrodman@gmail.com>
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
(cherry picked from commit b96dd1877c)

Co-authored-by: Sofia Papagiannaki <papagian@users.noreply.github.com>
2021-07-22 17:56:33 +03:00
Grot (@grafanabot)
4308a77e27 Auth: Pass user role to Grafana using auth proxy (#36729) (#37103)
* Pass role to Grafana using auth proxy

By default, the role will be applied to the default org of the user.
If the request uses the standard header "X-Grafana-Org-Id", the role will be applied to the specified org

Tested in both unit test and manually E2E

* Address comment: only allow the user role to be applied to the default org

Co-authored-by: Leonard Gram <leo@xlson.com>
(cherry picked from commit ad1f792b8b)

Co-authored-by: yuwaMSFT2 <yuwa@microsoft.com>
2021-07-22 16:25:51 +02:00
Grot (@grafanabot)
f18749927c "Release: Updated versions in package to 8.1.0-beta.1" (#37091) 2021-07-22 11:21:21 +02:00
63 changed files with 2789 additions and 627 deletions

View File

@@ -1,4 +1,91 @@
<!-- 8.1.0-beta1 START -->
# 8.1.0-beta1 (2021-07-22)
### Features and enhancements
* **Alerting:** Add Alertmanager notifications tab. [#35759](https://github.com/grafana/grafana/pull/35759), [@nathanrodman](https://github.com/nathanrodman)
* **Alerting:** Add button to deactivate current Alertmanager configuration. [#36951](https://github.com/grafana/grafana/pull/36951), [@domasx2](https://github.com/domasx2)
* **Alerting:** Add toggle in Loki/Prometheus data source configuration to opt out of alerting UI. [#36552](https://github.com/grafana/grafana/pull/36552), [@domasx2](https://github.com/domasx2)
* **Alerting:** Allow any "evaluate for" value >=0 in the alert rule form. [#35807](https://github.com/grafana/grafana/pull/35807), [@domasx2](https://github.com/domasx2)
* **Alerting:** Load default configuration from status endpoint, if Cortex Alertmanager returns empty user configuration. [#35769](https://github.com/grafana/grafana/pull/35769), [@domasx2](https://github.com/domasx2)
* **Alerting:** view to display alert rule and its underlying data. [#35546](https://github.com/grafana/grafana/pull/35546), [@mckn](https://github.com/mckn)
* **Annotation panel:** Release the annotation panel. [#36959](https://github.com/grafana/grafana/pull/36959), [@ryantxu](https://github.com/ryantxu)
* **Annotations:** Add typeahead support for tags in built-in annotations. [#36377](https://github.com/grafana/grafana/pull/36377), [@ashharrison90](https://github.com/ashharrison90)
* **AzureMonitor:** Add curated dashboards for Azure services. [#35356](https://github.com/grafana/grafana/pull/35356), [@avidhanju](https://github.com/avidhanju)
* **AzureMonitor:** Add support for deep links to Microsoft Azure portal for Metrics. [#32273](https://github.com/grafana/grafana/pull/32273), [@shuotli](https://github.com/shuotli)
* **AzureMonitor:** Remove support for different credentials for Azure Monitor Logs. [#35121](https://github.com/grafana/grafana/pull/35121), [@andresmgot](https://github.com/andresmgot)
* **AzureMonitor:** Support querying any Resource for Logs queries. [#33879](https://github.com/grafana/grafana/pull/33879), [@joshhunt](https://github.com/joshhunt)
* **Elasticsearch:** Add frozen indices search support. [#36018](https://github.com/grafana/grafana/pull/36018), [@Elfo404](https://github.com/Elfo404)
* **Elasticsearch:** Name fields after template variables values instead of their name. [#36035](https://github.com/grafana/grafana/pull/36035), [@Elfo404](https://github.com/Elfo404)
* **Elasticsearch:** add rate aggregation. [#33311](https://github.com/grafana/grafana/pull/33311), [@estermv](https://github.com/estermv)
* **Email:** Allow configuration of content types for email notifications. [#34530](https://github.com/grafana/grafana/pull/34530), [@djairhogeuens](https://github.com/djairhogeuens)
* **Explore:** Add more meta information when line limit is hit. [#33069](https://github.com/grafana/grafana/pull/33069), [@ivanahuckova](https://github.com/ivanahuckova)
* **Explore:** UI improvements to trace view. [#34276](https://github.com/grafana/grafana/pull/34276), [@aocenas](https://github.com/aocenas)
* **FieldOverrides:** Added support to change display name in an override field and have it be matched by a later rule. [#35893](https://github.com/grafana/grafana/pull/35893), [@torkelo](https://github.com/torkelo)
* **HTTP Client:** Introduce `dataproxy_max_idle_connections` config variable. [#35864](https://github.com/grafana/grafana/pull/35864), [@dsotirakis](https://github.com/dsotirakis)
* **InfluxDB:** InfluxQL: adds tags to timeseries data. [#36702](https://github.com/grafana/grafana/pull/36702), [@gabor](https://github.com/gabor)
* **InfluxDB:** InfluxQL: make measurement search case insensitive. [#34563](https://github.com/grafana/grafana/pull/34563), [@gabor](https://github.com/gabor)
* **Legacy Alerting:** Replace simplejson with a struct in webhook notification channel. [#34952](https://github.com/grafana/grafana/pull/34952), [@KEVISONG](https://github.com/KEVISONG)
* **Legend:** Updates display name for Last (not null) to just Last*. [#35633](https://github.com/grafana/grafana/pull/35633), [@torkelo](https://github.com/torkelo)
* **Logs panel:** Add option to show common labels. [#36166](https://github.com/grafana/grafana/pull/36166), [@ivanahuckova](https://github.com/ivanahuckova)
* **Loki:** Add $__range variable. [#36175](https://github.com/grafana/grafana/pull/36175), [@ivanahuckova](https://github.com/ivanahuckova)
* **Loki:** Add support for "label_values(log stream selector, label)" in templating. [#35488](https://github.com/grafana/grafana/pull/35488), [@ivanahuckova](https://github.com/ivanahuckova)
* **Loki:** Add support for ad-hoc filtering in dashboard. [#36393](https://github.com/grafana/grafana/pull/36393), [@ivanahuckova](https://github.com/ivanahuckova)
* **MySQL Datasource:** Add timezone parameter. [#27535](https://github.com/grafana/grafana/pull/27535), [@andipabst](https://github.com/andipabst)
* **NodeGraph:** Show gradient fields in legend. [#34078](https://github.com/grafana/grafana/pull/34078), [@aocenas](https://github.com/aocenas)
* **PanelOptions:** Don't mutate panel options/field config object when updating. [#36441](https://github.com/grafana/grafana/pull/36441), [@dprokop](https://github.com/dprokop)
* **PieChart:** Make pie gradient more subtle to match other charts. [#36961](https://github.com/grafana/grafana/pull/36961), [@nikki-kiga](https://github.com/nikki-kiga)
* **Prometheus:** Update PromQL typeahead and highlighting. [#36730](https://github.com/grafana/grafana/pull/36730), [@ekpdt](https://github.com/ekpdt)
* **Prometheus:** interpolate variable for step field. [#36437](https://github.com/grafana/grafana/pull/36437), [@zoltanbedi](https://github.com/zoltanbedi)
* **Provisioning:** Improve validation by validating across all dashboard providers. [#26742](https://github.com/grafana/grafana/pull/26742), [@nabokihms](https://github.com/nabokihms)
* **SQL Datasources:** Allow multiple string/labels columns with time series. [#36485](https://github.com/grafana/grafana/pull/36485), [@kylebrandt](https://github.com/kylebrandt)
* **Select:** Portal select menu to document.body. [#36398](https://github.com/grafana/grafana/pull/36398), [@ashharrison90](https://github.com/ashharrison90)
* **Team Sync:** Add group mapping to support team sync in the Generic OAuth provider. [#36307](https://github.com/grafana/grafana/pull/36307), [@wardbekker](https://github.com/wardbekker)
* **Tooltip:** Make active series more noticeable. [#36824](https://github.com/grafana/grafana/pull/36824), [@nikki-kiga](https://github.com/nikki-kiga)
* **Tracing:** Add support to configure trace to logs start and end time. [#34995](https://github.com/grafana/grafana/pull/34995), [@zoltanbedi](https://github.com/zoltanbedi)
* **Transformations:** Skip merge when there is only a single data frame. [#36407](https://github.com/grafana/grafana/pull/36407), [@edgarpoce](https://github.com/edgarpoce)
* **ValueMapping:** Added support for mapping text to color, boolean values, NaN and Null. Improved UI for value mapping. [#33820](https://github.com/grafana/grafana/pull/33820), [@torkelo](https://github.com/torkelo)
* **Visualizations:** Dynamically set any config (min, max, unit, color, thresholds) from query results. [#36548](https://github.com/grafana/grafana/pull/36548), [@torkelo](https://github.com/torkelo)
* **live:** Add support to handle origin without a value for the port when matching with root_url. [#36834](https://github.com/grafana/grafana/pull/36834), [@FZambia](https://github.com/FZambia)
### Bug fixes
* **Alerting:** Handle marshaling Inf values. [#36947](https://github.com/grafana/grafana/pull/36947), [@kylebrandt](https://github.com/kylebrandt)
* **AzureMonitor:** Fix macro resolution for template variables. [#36944](https://github.com/grafana/grafana/pull/36944), [@andresmgot](https://github.com/andresmgot)
* **AzureMonitor:** Fix queries with Microsoft.NetApp/../../volumes resources. [#32661](https://github.com/grafana/grafana/pull/32661), [@pckls](https://github.com/pckls)
* **AzureMonitor:** Request and concat subsequent resource pages. [#36958](https://github.com/grafana/grafana/pull/36958), [@andresmgot](https://github.com/andresmgot)
* **Bug:** Fix parse duration for day. [#36942](https://github.com/grafana/grafana/pull/36942), [@idafurjes](https://github.com/idafurjes)
* **Datasources:** Improve error handling for error messages. [#35120](https://github.com/grafana/grafana/pull/35120), [@ifrost](https://github.com/ifrost)
* **Explore:** Correct the functionality of shift-enter shortcut across all uses. [#36600](https://github.com/grafana/grafana/pull/36600), [@ivanahuckova](https://github.com/ivanahuckova)
* **Explore:** Show all dataFrames in data tab in Inspector. [#32161](https://github.com/grafana/grafana/pull/32161), [@ivanahuckova](https://github.com/ivanahuckova)
* **GraphNG:** Fix Tooltip mode 'All' for XYChart. [#31260](https://github.com/grafana/grafana/pull/31260), [@Posnet](https://github.com/Posnet)
* **Loki:** Fix highlight of logs when using filter expressions with backticks. [#36024](https://github.com/grafana/grafana/pull/36024), [@ivanahuckova](https://github.com/ivanahuckova)
* **Modal:** Force modal content to overflow with scroll. [#36754](https://github.com/grafana/grafana/pull/36754), [@ashharrison90](https://github.com/ashharrison90)
* **Plugins:** Ignore symlinked folders when verifying plugin signature. [#34434](https://github.com/grafana/grafana/pull/34434), [@wbrowne](https://github.com/wbrowne)
### Breaking changes
When parsing Elasticsearch query responses using template variables, each field gets named after the variable value instead of the name.
For example, executing a `terms` aggregation on a variable named `$groupBy` that has `@hostname` as a value, the resulting column in the table response will be `@hostname` instead of `$groupBy` Issue [#36035](https://github.com/grafana/grafana/issues/36035)
Azure Monitor data source no longer supports different credentials for Metrics and Logs in existing data sources. To use different credentials for Azure Monitor Logs, create another data source. Issue [#35121](https://github.com/grafana/grafana/issues/35121)
Existing Azure Metrics Logs queries for Log Analytics Workspaces should be backward compatible with this change and should not get impacted. Panels will be migrated to use the new resource-centric backend when you first edit and save them.
Application Insights and Insights Analytics queries are now read-only and cannot be modified. To update Application Insights queries, users can manually recreate them as Metrics queries, and Insights Analytics are recreated with Logs.
Issue [#33879](https://github.com/grafana/grafana/issues/33879)
### Plugin development fixes & changes
* **Toolkit:** Improve error messages when tasks fail. [#36381](https://github.com/grafana/grafana/pull/36381), [@joshhunt](https://github.com/joshhunt)
<!-- 8.1.0-beta1 END -->
<!-- 8.0.6 START -->
# 8.0.6 (2021-07-14)

View File

@@ -995,12 +995,12 @@
[geomap]
# Set the JSON configuration for the default basemap
;default_baselayer_config = `{
"type": "xyz",
"config": {
"attribution": "Open street map",
"url": "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
}
}`
; "type": "xyz",
; "config": {
; "attribution": "Open street map",
; "url": "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
; }
;}`
# Enable or disable loading other base map layers
;enable_custom_baselayers = true

View File

@@ -109,8 +109,9 @@ The following template variables are available when expanding annotations and la
| Name | Description |
| ------- | --------------- |
| $labels | Labels contains the labels from the query or condition. For example, `{{ $labels.instance }}` and `{{ $labels.job }}`. |
| $values | Values contains the values of all reduce and math expressions that were evaluated for this alert rule. For example, `{{ $values.A }}`, `{{ $values.A.Labels }}` and `{{ $values.A.Value }}` where `A` is the `refID` of the expression. |
| $labels | The labels from the query or condition. For example, `{{ $labels.instance }}` and `{{ $labels.job }}`. |
| $values | The values of all reduce and math expressions that were evaluated for this alert rule. For example, `{{ $values.A }}`, `{{ $values.A.Labels }}` and `{{ $values.A.Value }}` where `A` is the `refID` of the expression. |
| $value | The value string of the alert instance. For example, `[ var='A' labels={instance=foo} value=10 ]`. |
## Preview alerts

View File

@@ -30,7 +30,7 @@ sync_ttl = 60
# Example `whitelist = 192.168.1.1, 192.168.1.0/24, 2001::23, 2001::0/120`
whitelist =
# Optionally define more headers to sync other user attributes
# Example `headers = Name:X-WEBAUTH-NAME Email:X-WEBAUTH-EMAIL Groups:X-WEBAUTH-GROUPS`
# Example `headers = Name:X-WEBAUTH-NAME Role:X-WEBAUTH-ROLE Email:X-WEBAUTH-EMAIL Groups:X-WEBAUTH-GROUPS`
headers =
# Check out docs on this for more details on the below setting
enable_login_token = false

View File

@@ -237,7 +237,9 @@ Content-Length: 97
`DELETE /api/folders/:uid`
Deletes an existing folder identified by uid together with all dashboards stored in the folder, if any. This operation cannot be reverted.
Deletes an existing folder identified by UID along with all dashboards (and their alerts) stored in the folder. This operation cannot be reverted.
If [Grafana 8 Alerts]({{< relref "../alerting/unified-alerting/_index.md" >}}) are enabled, you can set an optional query parameter `forceDeleteRules=false` so that requests will fail with 400 (Bad Request) error if the folder contains any Grafana 8 Alerts. However, if this parameter is set to `true` then it will delete any Grafana 8 Alerts under this folder.
**Example Request**:
@@ -265,6 +267,7 @@ Status Codes:
- **200** Deleted
- **401** Unauthorized
- **400** Bad Request
- **403** Access Denied
- **404** Folder not found

View File

@@ -0,0 +1,91 @@
+++
title = "Release notes for Grafana 8.1.0-beta1"
[_build]
list = false
+++
<!-- Auto generated by update changelog github action -->
# Release notes for Grafana 8.1.0-beta1
### Features and enhancements
* **Alerting:** Add Alertmanager notifications tab. [#35759](https://github.com/grafana/grafana/pull/35759), [@nathanrodman](https://github.com/nathanrodman)
* **Alerting:** Add button to deactivate current Alertmanager configuration. [#36951](https://github.com/grafana/grafana/pull/36951), [@domasx2](https://github.com/domasx2)
* **Alerting:** Add toggle in Loki/Prometheus data source configuration to opt out of alerting UI. [#36552](https://github.com/grafana/grafana/pull/36552), [@domasx2](https://github.com/domasx2)
* **Alerting:** Allow any "evaluate for" value >=0 in the alert rule form. [#35807](https://github.com/grafana/grafana/pull/35807), [@domasx2](https://github.com/domasx2)
* **Alerting:** Load default configuration from status endpoint, if Cortex Alertmanager returns empty user configuration. [#35769](https://github.com/grafana/grafana/pull/35769), [@domasx2](https://github.com/domasx2)
* **Alerting:** view to display alert rule and its underlying data. [#35546](https://github.com/grafana/grafana/pull/35546), [@mckn](https://github.com/mckn)
* **Annotation panel:** Release the annotation panel. [#36959](https://github.com/grafana/grafana/pull/36959), [@ryantxu](https://github.com/ryantxu)
* **Annotations:** Add typeahead support for tags in built-in annotations. [#36377](https://github.com/grafana/grafana/pull/36377), [@ashharrison90](https://github.com/ashharrison90)
* **AzureMonitor:** Add curated dashboards for Azure services. [#35356](https://github.com/grafana/grafana/pull/35356), [@avidhanju](https://github.com/avidhanju)
* **AzureMonitor:** Add support for deep links to Microsoft Azure portal for Metrics. [#32273](https://github.com/grafana/grafana/pull/32273), [@shuotli](https://github.com/shuotli)
* **AzureMonitor:** Remove support for different credentials for Azure Monitor Logs. [#35121](https://github.com/grafana/grafana/pull/35121), [@andresmgot](https://github.com/andresmgot)
* **AzureMonitor:** Support querying any Resource for Logs queries. [#33879](https://github.com/grafana/grafana/pull/33879), [@joshhunt](https://github.com/joshhunt)
* **Elasticsearch:** Add frozen indices search support. [#36018](https://github.com/grafana/grafana/pull/36018), [@Elfo404](https://github.com/Elfo404)
* **Elasticsearch:** Name fields after template variables values instead of their name. [#36035](https://github.com/grafana/grafana/pull/36035), [@Elfo404](https://github.com/Elfo404)
* **Elasticsearch:** add rate aggregation. [#33311](https://github.com/grafana/grafana/pull/33311), [@estermv](https://github.com/estermv)
* **Email:** Allow configuration of content types for email notifications. [#34530](https://github.com/grafana/grafana/pull/34530), [@djairhogeuens](https://github.com/djairhogeuens)
* **Explore:** Add more meta information when line limit is hit. [#33069](https://github.com/grafana/grafana/pull/33069), [@ivanahuckova](https://github.com/ivanahuckova)
* **Explore:** UI improvements to trace view. [#34276](https://github.com/grafana/grafana/pull/34276), [@aocenas](https://github.com/aocenas)
* **FieldOverrides:** Added support to change display name in an override field and have it be matched by a later rule. [#35893](https://github.com/grafana/grafana/pull/35893), [@torkelo](https://github.com/torkelo)
* **HTTP Client:** Introduce `dataproxy_max_idle_connections` config variable. [#35864](https://github.com/grafana/grafana/pull/35864), [@dsotirakis](https://github.com/dsotirakis)
* **InfluxDB:** InfluxQL: adds tags to timeseries data. [#36702](https://github.com/grafana/grafana/pull/36702), [@gabor](https://github.com/gabor)
* **InfluxDB:** InfluxQL: make measurement search case insensitive. [#34563](https://github.com/grafana/grafana/pull/34563), [@gabor](https://github.com/gabor)
* **Legacy Alerting:** Replace simplejson with a struct in webhook notification channel. [#34952](https://github.com/grafana/grafana/pull/34952), [@KEVISONG](https://github.com/KEVISONG)
* **Legend:** Updates display name for Last (not null) to just Last*. [#35633](https://github.com/grafana/grafana/pull/35633), [@torkelo](https://github.com/torkelo)
* **Logs panel:** Add option to show common labels. [#36166](https://github.com/grafana/grafana/pull/36166), [@ivanahuckova](https://github.com/ivanahuckova)
* **Loki:** Add $__range variable. [#36175](https://github.com/grafana/grafana/pull/36175), [@ivanahuckova](https://github.com/ivanahuckova)
* **Loki:** Add support for "label_values(log stream selector, label)" in templating. [#35488](https://github.com/grafana/grafana/pull/35488), [@ivanahuckova](https://github.com/ivanahuckova)
* **Loki:** Add support for ad-hoc filtering in dashboard. [#36393](https://github.com/grafana/grafana/pull/36393), [@ivanahuckova](https://github.com/ivanahuckova)
* **MySQL Datasource:** Add timezone parameter. [#27535](https://github.com/grafana/grafana/pull/27535), [@andipabst](https://github.com/andipabst)
* **NodeGraph:** Show gradient fields in legend. [#34078](https://github.com/grafana/grafana/pull/34078), [@aocenas](https://github.com/aocenas)
* **PanelOptions:** Don't mutate panel options/field config object when updating. [#36441](https://github.com/grafana/grafana/pull/36441), [@dprokop](https://github.com/dprokop)
* **PieChart:** Make pie gradient more subtle to match other charts. [#36961](https://github.com/grafana/grafana/pull/36961), [@nikki-kiga](https://github.com/nikki-kiga)
* **Prometheus:** Update PromQL typeahead and highlighting. [#36730](https://github.com/grafana/grafana/pull/36730), [@ekpdt](https://github.com/ekpdt)
* **Prometheus:** interpolate variable for step field. [#36437](https://github.com/grafana/grafana/pull/36437), [@zoltanbedi](https://github.com/zoltanbedi)
* **Provisioning:** Improve validation by validating across all dashboard providers. [#26742](https://github.com/grafana/grafana/pull/26742), [@nabokihms](https://github.com/nabokihms)
* **SQL Datasources:** Allow multiple string/labels columns with time series. [#36485](https://github.com/grafana/grafana/pull/36485), [@kylebrandt](https://github.com/kylebrandt)
* **Select:** Portal select menu to document.body. [#36398](https://github.com/grafana/grafana/pull/36398), [@ashharrison90](https://github.com/ashharrison90)
* **Team Sync:** Add group mapping to support team sync in the Generic OAuth provider. [#36307](https://github.com/grafana/grafana/pull/36307), [@wardbekker](https://github.com/wardbekker)
* **Tooltip:** Make active series more noticeable. [#36824](https://github.com/grafana/grafana/pull/36824), [@nikki-kiga](https://github.com/nikki-kiga)
* **Tracing:** Add support to configure trace to logs start and end time. [#34995](https://github.com/grafana/grafana/pull/34995), [@zoltanbedi](https://github.com/zoltanbedi)
* **Transformations:** Skip merge when there is only a single data frame. [#36407](https://github.com/grafana/grafana/pull/36407), [@edgarpoce](https://github.com/edgarpoce)
* **ValueMapping:** Added support for mapping text to color, boolean values, NaN and Null. Improved UI for value mapping. [#33820](https://github.com/grafana/grafana/pull/33820), [@torkelo](https://github.com/torkelo)
* **Visualizations:** Dynamically set any config (min, max, unit, color, thresholds) from query results. [#36548](https://github.com/grafana/grafana/pull/36548), [@torkelo](https://github.com/torkelo)
* **live:** Add support to handle origin without a value for the port when matching with root_url. [#36834](https://github.com/grafana/grafana/pull/36834), [@FZambia](https://github.com/FZambia)
### Bug fixes
* **Alerting:** Handle marshaling Inf values. [#36947](https://github.com/grafana/grafana/pull/36947), [@kylebrandt](https://github.com/kylebrandt)
* **AzureMonitor:** Fix macro resolution for template variables. [#36944](https://github.com/grafana/grafana/pull/36944), [@andresmgot](https://github.com/andresmgot)
* **AzureMonitor:** Fix queries with Microsoft.NetApp/../../volumes resources. [#32661](https://github.com/grafana/grafana/pull/32661), [@pckls](https://github.com/pckls)
* **AzureMonitor:** Request and concat subsequent resource pages. [#36958](https://github.com/grafana/grafana/pull/36958), [@andresmgot](https://github.com/andresmgot)
* **Bug:** Fix parse duration for day. [#36942](https://github.com/grafana/grafana/pull/36942), [@idafurjes](https://github.com/idafurjes)
* **Datasources:** Improve error handling for error messages. [#35120](https://github.com/grafana/grafana/pull/35120), [@ifrost](https://github.com/ifrost)
* **Explore:** Correct the functionality of shift-enter shortcut across all uses. [#36600](https://github.com/grafana/grafana/pull/36600), [@ivanahuckova](https://github.com/ivanahuckova)
* **Explore:** Show all dataFrames in data tab in Inspector. [#32161](https://github.com/grafana/grafana/pull/32161), [@ivanahuckova](https://github.com/ivanahuckova)
* **GraphNG:** Fix Tooltip mode 'All' for XYChart. [#31260](https://github.com/grafana/grafana/pull/31260), [@Posnet](https://github.com/Posnet)
* **Loki:** Fix highlight of logs when using filter expressions with backticks. [#36024](https://github.com/grafana/grafana/pull/36024), [@ivanahuckova](https://github.com/ivanahuckova)
* **Modal:** Force modal content to overflow with scroll. [#36754](https://github.com/grafana/grafana/pull/36754), [@ashharrison90](https://github.com/ashharrison90)
* **Plugins:** Ignore symlinked folders when verifying plugin signature. [#34434](https://github.com/grafana/grafana/pull/34434), [@wbrowne](https://github.com/wbrowne)
### Breaking changes
When parsing Elasticsearch query responses using template variables, each field gets named after the variable value instead of the name.
For example, executing a `terms` aggregation on a variable named `$groupBy` that has `@hostname` as a value, the resulting column in the table response will be `@hostname` instead of `$groupBy` Issue [#36035](https://github.com/grafana/grafana/issues/36035)
Azure Monitor data source no longer supports different credentials for Metrics and Logs in existing data sources. To use different credentials for Azure Monitor Logs, create another data source. Issue [#35121](https://github.com/grafana/grafana/issues/35121)
Existing Azure Metrics Logs queries for Log Analytics Workspaces should be backward compatible with this change and should not get impacted. Panels will be migrated to use the new resource-centric backend when you first edit and save them.
Application Insights and Insights Analytics queries are now read-only and cannot be modified. To update Application Insights queries, users can manually recreate them as Metrics queries, and Insights Analytics are recreated with Logs.
Issue [#33879](https://github.com/grafana/grafana/issues/33879)
### Plugin development fixes & changes
* **Toolkit:** Improve error messages when tasks fail. [#36381](https://github.com/grafana/grafana/pull/36381), [@joshhunt](https://github.com/joshhunt)

View File

@@ -11,6 +11,7 @@ as info on deprecations, breaking changes and plugin development read the [relea
## Grafana 8
- [What's new in 8.1]({{< relref "whats-new-in-v8-1" >}})
- [What's new in 8.0]({{< relref "whats-new-in-v8-0" >}})
## Grafana 7

View File

@@ -0,0 +1,79 @@
+++
title = "What's new in Grafana v8.1"
description = "Feature and improvement highlights for Grafana v8.1"
keywords = ["grafana", "new", "documentation", "8.1", "release notes"]
weight = -33
aliases = ["/docs/grafana/latest/guides/whats-new-in-v8-1/"]
[_build]
list = false
+++
# Whats new in Grafana v8.1
> **Note:** This topic will be updated frequently between now and the final release.
This topic includes the release notes for Grafana v8.1. For all details, read the full [CHANGELOG.md](https://github.com/grafana/grafana/blob/master/CHANGELOG.md).
## Grafana OSS features
These features are included in the Grafana open source edition.
### Time series panel updates
Time series panels have been updated with the ability to color series and line by thresholds or gradient color scales. This allows users to create time series panels where the line color can change dynamically based on thresholds or using gradient color scales.
Also, we have added possibility to create annotations directly from the panel. For more information, refer to ...
### Geomap panel
Grafana 8.1 introduces the foundation for our new map panel. This new panel leverages [OpenLayers](https://openlayers.org/) and gives us a flexible solution for extending the way we use the new Geomap panel moving forward. We expect to ship this new visualization with the ability to use [Circle Overlays](https://github.com/grafana/grafana/pull/36680) and [Heatmaps](https://github.com/open-o11y/grafana/pull/18).
For more information, refer to [issue 36585](https://github.com/grafana/grafana/issues/36585). For documentation, refer to ...
### Annotation panel
This section is for the new panel...
### Transformations improvements
Grafana 8.1 includes many transformations enhancements.
#### Config from query (Beta)
This transformation enables panel config (Threshold, Min, Max, etc.) to be derived from query results. For more information, refer to [Config from query results transform]({{< relref "../panels/transformations/config-from-query.md" >}}).
#### Rows to fields (Beta)
This transformation enables rows in returned data to be converted into separate fields. Prior to this enhancement, you could style and configure fields individually, but not rows. For more information, refer to [Rows to fields transform]({{< relref "../panels/transformations/rows-to-fields.md" >}}).
#### Contextual & Inline Help
Additional inline help will be available for Transformations. We can now share examples of how to use specific transformations and point users directly to the appropriate place in the docs for more information.
### Data source updates
The following data source updates are included with this Grafana release.
#### MySQL Data Source
We have added timezone support. As a result, you can now specify the time zone used in the database session, such as `Europe/Berlin` or `+02:00`.
### Trace to logs improvements
We have updated the default behavior from creating a one (1) hour span Loki query to only query at the exact time the trace span started for the duration of it. For more fine grained control over this you can shift this time in the tracing data source settings. It is now possible to to shift the start time and end time of the Loki query by the set amount. For more information, refer to [Trace to logs]({{< relref "../datasources/tempo.md#trace-to-logs" >}}).
#### Documentation updates
New panel summaries and preview on the top level [Visualizations]({{< relref "../panels/visualizations/_index.md" >}}) page to help users pick or learn about specific visualizations more easily.
## Enterprise features
These features are included in the Grafana Enterprise edition.
### Oauth2 - Team Sync to Group Mapping
With Team Sync you can map your Generic OAuth groups to teams in Grafana so that the users are automatically added to the correct teams.

View File

@@ -1,6 +1,8 @@
{
"npmClient": "yarn",
"useWorkspaces": true,
"packages": ["packages/*"],
"version": "8.1.0-pre"
"packages": [
"packages/*"
],
"version": "8.1.0-beta.2"
}

View File

@@ -3,7 +3,7 @@
"license": "AGPL-3.0-only",
"private": true,
"name": "grafana",
"version": "8.1.0-pre",
"version": "8.1.0-beta.2",
"repository": "github:grafana/grafana",
"scripts": {
"api-tests": "jest --notify --watch --config=devenv/e2e-api-tests/jest.js",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/data",
"version": "8.1.0-pre",
"version": "8.1.0-beta.2",
"description": "Grafana Data Library",
"keywords": [
"typescript"

View File

@@ -644,7 +644,7 @@ describe('getLinksSupplier', () => {
expect(links[0]).toEqual(
expect.objectContaining({
title: 'testDS',
href: '/explore?left={"datasource":"testDS","queries":["12345"]}',
href: `/explore?left=${encodeURIComponent('{"datasource":"testDS","queries":["12345"]}')}`,
onClick: undefined,
})
);

View File

@@ -31,7 +31,7 @@ describe('mapInternalLinkToExplore', () => {
expect(link).toEqual(
expect.objectContaining({
title: 'dsName',
href: '/explore?left={"datasource":"dsName","queries":[{"query":"12344"}]}',
href: `/explore?left=${encodeURIComponent('{"datasource":"dsName","queries":[{"query":"12344"}]}')}`,
onClick: undefined,
})
);

View File

@@ -67,11 +67,13 @@ export function mapInternalLinkToExplore(options: LinkToExploreOptions): LinkMod
*/
function generateInternalHref<T extends DataQuery = any>(datasourceName: string, query: T, range: TimeRange): string {
return locationUtil.assureBaseUrl(
`/explore?left=${serializeStateToUrlParam({
range: range.raw,
datasource: datasourceName,
queries: [query],
})}`
`/explore?left=${encodeURIComponent(
serializeStateToUrlParam({
range: range.raw,
datasource: datasourceName,
queries: [query],
})
)}`
);
}

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/e2e-selectors",
"version": "8.1.0-pre",
"version": "8.1.0-beta.2",
"description": "Grafana End-to-End Test Selectors Library",
"keywords": [
"cli",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/e2e",
"version": "8.1.0-pre",
"version": "8.1.0-beta.2",
"description": "Grafana End-to-End Test Library",
"keywords": [
"cli",
@@ -45,7 +45,7 @@
"types": "src/index.ts",
"dependencies": {
"@cypress/webpack-preprocessor": "5.9.0",
"@grafana/e2e-selectors": "8.1.0-pre",
"@grafana/e2e-selectors": "8.1.0-beta.2",
"@grafana/tsconfig": "^1.0.0-rc1",
"@mochajs/json-file-reporter": "^1.2.0",
"blink-diff": "1.0.13",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/runtime",
"version": "8.1.0-pre",
"version": "8.1.0-beta.2",
"description": "Grafana Runtime Library",
"keywords": [
"grafana",
@@ -22,9 +22,9 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@grafana/data": "8.1.0-pre",
"@grafana/e2e-selectors": "8.1.0-pre",
"@grafana/ui": "8.1.0-pre",
"@grafana/data": "8.1.0-beta.2",
"@grafana/e2e-selectors": "8.1.0-beta.2",
"@grafana/ui": "8.1.0-beta.2",
"history": "4.10.1",
"systemjs": "0.20.19",
"systemjs-plugin-css": "0.1.37"

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/toolkit",
"version": "8.1.0-pre",
"version": "8.1.0-beta.2",
"description": "Grafana Toolkit",
"keywords": [
"grafana",
@@ -28,10 +28,10 @@
"dependencies": {
"@babel/core": "7.13.14",
"@babel/preset-env": "7.13.12",
"@grafana/data": "8.1.0-pre",
"@grafana/data": "8.1.0-beta.2",
"@grafana/eslint-config": "2.5.0",
"@grafana/tsconfig": "^1.0.0-rc1",
"@grafana/ui": "8.1.0-pre",
"@grafana/ui": "8.1.0-beta.2",
"@types/command-exists": "^1.2.0",
"@types/expect-puppeteer": "3.3.1",
"@types/fs-extra": "^8.1.0",

View File

@@ -18,6 +18,7 @@ module.exports = {
backgrounds: false,
},
},
'@storybook/addon-a11y',
'@storybook/addon-knobs',
'@storybook/addon-storysource',
'storybook-dark-mode',

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/ui",
"version": "8.1.0-pre",
"version": "8.1.0-beta.2",
"description": "Grafana Components Library",
"keywords": [
"grafana",
@@ -29,8 +29,8 @@
"@emotion/css": "11.1.3",
"@emotion/react": "11.1.5",
"@grafana/aws-sdk": "0.0.3",
"@grafana/data": "8.1.0-pre",
"@grafana/e2e-selectors": "8.1.0-pre",
"@grafana/data": "8.1.0-beta.2",
"@grafana/e2e-selectors": "8.1.0-beta.2",
"@grafana/slate-react": "0.22.10-grafana",
"@grafana/tsconfig": "^1.0.0-rc1",
"@monaco-editor/react": "4.1.1",
@@ -79,11 +79,12 @@
"@rollup/plugin-commonjs": "16.0.0",
"@rollup/plugin-image": "2.0.5",
"@rollup/plugin-node-resolve": "10.0.0",
"@storybook/addon-essentials": "6.3.0",
"@storybook/addon-a11y": "6.3.5",
"@storybook/addon-essentials": "6.3.5",
"@storybook/addon-knobs": "6.3.0",
"@storybook/addon-storysource": "6.3.0",
"@storybook/react": "6.3.0",
"@storybook/theming": "6.3.0",
"@storybook/addon-storysource": "6.3.5",
"@storybook/react": "6.3.5",
"@storybook/theming": "6.3.5",
"@testing-library/jest-dom": "5.11.9",
"@types/classnames": "2.2.7",
"@types/common-tags": "^1.8.0",

View File

@@ -56,7 +56,14 @@ const HttpAccessHelp = () => (
);
export const DataSourceHttpSettings: React.FC<HttpSettingsProps> = (props) => {
const { defaultUrl, dataSourceConfig, onChange, showAccessOptions, sigV4AuthToggleEnabled } = props;
const {
defaultUrl,
dataSourceConfig,
onChange,
showAccessOptions,
sigV4AuthToggleEnabled,
azureAuthSettings,
} = props;
let urlTooltip;
const [isAccessHelpVisible, setIsAccessHelpVisible] = useState(false);
const theme = useTheme();
@@ -207,6 +214,22 @@ export const DataSourceHttpSettings: React.FC<HttpSettingsProps> = (props) => {
/>
</div>
{azureAuthSettings?.azureAuthEnabled && (
<div className="gf-form-inline">
<Switch
label="Azure Authentication"
labelClass="width-13"
checked={dataSourceConfig.jsonData.azureAuth || false}
onChange={(event) => {
onSettingsChange({
jsonData: { ...dataSourceConfig.jsonData, azureAuth: event!.currentTarget.checked },
});
}}
tooltip="Use Azure authentication for Azure endpoint."
/>
</div>
)}
{sigV4AuthToggleEnabled && (
<div className="gf-form-inline">
<Switch
@@ -238,6 +261,12 @@ export const DataSourceHttpSettings: React.FC<HttpSettingsProps> = (props) => {
</>
)}
{azureAuthSettings?.azureAuthEnabled &&
azureAuthSettings?.azureSettingsUI &&
dataSourceConfig.jsonData.azureAuth && (
<azureAuthSettings.azureSettingsUI dataSourceConfig={dataSourceConfig} onChange={onChange} />
)}
{dataSourceConfig.jsonData.sigV4Auth && <SigV4AuthSettings {...props} />}
{(dataSourceConfig.jsonData.tlsAuth || dataSourceConfig.jsonData.tlsAuthWithCACert) && (

View File

@@ -1,5 +1,11 @@
import React from 'react';
import { DataSourceSettings } from '@grafana/data';
export interface AzureAuthSettings {
azureAuthEnabled: boolean;
azureSettingsUI?: React.ComponentType<HttpSettingsBaseProps>;
}
export interface HttpSettingsBaseProps {
/** The configuration object of the data source */
dataSourceConfig: DataSourceSettings<any, any>;
@@ -14,4 +20,6 @@ export interface HttpSettingsProps extends HttpSettingsBaseProps {
showAccessOptions?: boolean;
/** Show the SigV4 auth toggle option */
sigV4AuthToggleEnabled?: boolean;
/** Azure authentication settings **/
azureAuthSettings?: AzureAuthSettings;
}

View File

@@ -7,7 +7,7 @@ import { useTheme } from '../../themes';
import mdx from './Icon.mdx';
export default {
title: 'General/Icon',
title: 'Docs overview/Icon',
component: Icon,
decorators: [withCenteredStory],
parameters: {

View File

@@ -26,17 +26,32 @@ export interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {
tooltipPlacement?: TooltipPlacement;
/** Variant to change the color of the Icon */
variant?: IconButtonVariant;
/** Text avilable ony for screenscreen readers. Will use tooltip text as fallback. */
ariaLabel?: string;
}
type SurfaceType = 'dashboard' | 'panel' | 'header';
export const IconButton = React.forwardRef<HTMLButtonElement, Props>(
({ name, size = 'md', iconType, tooltip, tooltipPlacement, className, variant = 'secondary', ...restProps }, ref) => {
(
{
name,
size = 'md',
iconType,
tooltip,
tooltipPlacement,
ariaLabel,
className,
variant = 'secondary',
...restProps
},
ref
) => {
const theme = useTheme2();
const styles = getStyles(theme, size, variant);
const button = (
<button ref={ref} {...restProps} className={cx(styles.button, className)}>
<button ref={ref} aria-label={ariaLabel || tooltip || ''} {...restProps} className={cx(styles.button, className)}>
<Icon name={name} size={size} className={styles.icon} type={iconType} />
</button>
);

View File

@@ -93,7 +93,7 @@ export class Sparkline extends PureComponent<SparklineProps, State> {
const builder = new UPlotConfigBuilder();
builder.setCursor({
show: true,
show: false,
x: false, // no crosshairs
y: false,
});

View File

@@ -1,6 +1,6 @@
{
"name": "@jaegertracing/jaeger-ui-components",
"version": "8.1.0-pre",
"version": "8.1.0-beta.2",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -16,8 +16,8 @@
"dependencies": {
"@emotion/css": "11.1.3",
"@emotion/react": "11.1.5",
"@grafana/data": "8.1.0-pre",
"@grafana/ui": "8.1.0-pre",
"@grafana/data": "8.1.0-beta.2",
"@grafana/ui": "8.1.0-beta.2",
"@types/classnames": "^2.2.7",
"@types/deep-freeze": "^0.1.1",
"@types/hoist-non-react-statics": "^3.3.1",

View File

@@ -96,7 +96,7 @@ func (hs *HTTPServer) DeleteFolder(c *models.ReqContext) response.Response { //
return ToFolderErrorResponse(err)
}
f, err := s.DeleteFolder(c.Params(":uid"))
f, err := s.DeleteFolder(c.Params(":uid"), c.QueryBool("forceDeleteRules"))
if err != nil {
return ToFolderErrorResponse(err)
}
@@ -149,7 +149,8 @@ func ToFolderErrorResponse(err error) response.Response {
if errors.Is(err, models.ErrFolderTitleEmpty) ||
errors.Is(err, models.ErrDashboardTypeMismatch) ||
errors.Is(err, models.ErrDashboardInvalidUid) ||
errors.Is(err, models.ErrDashboardUidTooLong) {
errors.Is(err, models.ErrDashboardUidTooLong) ||
errors.Is(err, models.ErrFolderContainsAlertRules) {
return response.Error(400, err.Error(), nil)
}

View File

@@ -238,7 +238,7 @@ func (s *fakeFolderService) UpdateFolder(existingUID string, cmd *models.UpdateF
return s.UpdateFolderError
}
func (s *fakeFolderService) DeleteFolder(uid string) (*models.Folder, error) {
func (s *fakeFolderService) DeleteFolder(uid string, forceDeleteRules bool) (*models.Folder, error) {
s.DeletedFolderUids = append(s.DeletedFolderUids, uid)
return s.DeleteFolderResult, s.DeleteFolderError
}

View File

@@ -0,0 +1,113 @@
package httpclientprovider
import (
"fmt"
"net/http"
"net/url"
"path"
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/aztokenprovider"
)
const azureMiddlewareName = "AzureAuthentication.Provider"
func AzureMiddleware(cfg *setting.Cfg) httpclient.Middleware {
return httpclient.NamedMiddlewareFunc(azureMiddlewareName, func(opts httpclient.Options, next http.RoundTripper) http.RoundTripper {
if enabled, err := isAzureAuthenticationEnabled(opts.CustomOptions); err != nil {
return errorResponse(err)
} else if !enabled {
return next
}
credentials, err := getAzureCredentials(opts.CustomOptions)
if err != nil {
return errorResponse(err)
} else if credentials == nil {
credentials = getDefaultAzureCredentials(cfg)
}
tokenProvider, err := aztokenprovider.NewAzureAccessTokenProvider(cfg, credentials)
if err != nil {
return errorResponse(err)
}
scopes, err := getAzureEndpointScopes(opts.CustomOptions)
if err != nil {
return errorResponse(err)
}
return aztokenprovider.ApplyAuth(tokenProvider, scopes, next)
})
}
func errorResponse(err error) http.RoundTripper {
return httpclient.RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
return nil, fmt.Errorf("invalid Azure configuration: %s", err)
})
}
func isAzureAuthenticationEnabled(customOptions map[string]interface{}) (bool, error) {
if untypedValue, ok := customOptions["_azureAuth"]; !ok {
return false, nil
} else if value, ok := untypedValue.(bool); !ok {
err := fmt.Errorf("the field 'azureAuth' should be a bool")
return false, err
} else {
return value, nil
}
}
func getAzureCredentials(customOptions map[string]interface{}) (azcredentials.AzureCredentials, error) {
if untypedValue, ok := customOptions["_azureCredentials"]; !ok {
return nil, nil
} else if value, ok := untypedValue.(azcredentials.AzureCredentials); !ok {
err := fmt.Errorf("the field 'azureCredentials' should be a valid credentials object")
return nil, err
} else {
return value, nil
}
}
func getDefaultAzureCredentials(cfg *setting.Cfg) azcredentials.AzureCredentials {
if cfg.Azure.ManagedIdentityEnabled {
return &azcredentials.AzureManagedIdentityCredentials{}
} else {
return &azcredentials.AzureClientSecretCredentials{
AzureCloud: cfg.Azure.Cloud,
}
}
}
func getAzureEndpointResourceId(customOptions map[string]interface{}) (*url.URL, error) {
var value string
if untypedValue, ok := customOptions["azureEndpointResourceId"]; !ok {
err := fmt.Errorf("the field 'azureEndpointResourceId' should be set")
return nil, err
} else if value, ok = untypedValue.(string); !ok {
err := fmt.Errorf("the field 'azureEndpointResourceId' should be a string")
return nil, err
}
resourceId, err := url.Parse(value)
if err != nil || resourceId.Scheme == "" || resourceId.Host == "" {
err := fmt.Errorf("invalid endpoint Resource ID URL '%s'", value)
return nil, err
}
return resourceId, nil
}
func getAzureEndpointScopes(customOptions map[string]interface{}) ([]string, error) {
resourceId, err := getAzureEndpointResourceId(customOptions)
if err != nil {
return nil, err
}
resourceId.Path = path.Join(resourceId.Path, ".default")
scopes := []string{resourceId.String()}
return scopes, nil
}

View File

@@ -34,6 +34,10 @@ func New(cfg *setting.Cfg) httpclient.Provider {
setDefaultTimeoutOptions(cfg)
if cfg.FeatureToggles["httpclientprovider_azure_auth"] {
middlewares = append(middlewares, AzureMiddleware(cfg))
}
return newProviderFunc(sdkhttpclient.ProviderOptions{
Middlewares: middlewares,
ConfigureTransport: func(opts sdkhttpclient.Options, transport *http.Transport) {

View File

@@ -7,6 +7,7 @@ import (
"net"
"net/http"
"path/filepath"
"strconv"
"testing"
"time"
@@ -349,6 +350,8 @@ func TestMiddlewareContext(t *testing.T) {
t.Run("auth_proxy", func(t *testing.T) {
const userID int64 = 33
const orgID int64 = 4
const defaultOrgId int64 = 1
const orgRole = "Admin"
configure := func(cfg *setting.Cfg) {
cfg.AuthProxyEnabled = true
@@ -356,7 +359,7 @@ func TestMiddlewareContext(t *testing.T) {
cfg.LDAPEnabled = true
cfg.AuthProxyHeaderName = "X-WEBAUTH-USER"
cfg.AuthProxyHeaderProperty = "username"
cfg.AuthProxyHeaders = map[string]string{"Groups": "X-WEBAUTH-GROUPS"}
cfg.AuthProxyHeaders = map[string]string{"Groups": "X-WEBAUTH-GROUPS", "Role": "X-WEBAUTH-ROLE"}
}
const hdrName = "markelog"
@@ -432,6 +435,71 @@ func TestMiddlewareContext(t *testing.T) {
cfg.AuthProxyAutoSignUp = true
})
middlewareScenario(t, "Should assign role from header to default org", func(t *testing.T, sc *scenarioContext) {
var storedRoleInfo map[int64]models.RoleType = nil
bus.AddHandlerCtx("test", func(ctx context.Context, query *models.GetSignedInUserQuery) error {
if query.UserId > 0 {
query.Result = &models.SignedInUser{OrgId: defaultOrgId, UserId: userID, OrgRole: storedRoleInfo[defaultOrgId]}
return nil
}
return models.ErrUserNotFound
})
bus.AddHandler("test", func(cmd *models.UpsertUserCommand) error {
cmd.Result = &models.User{Id: userID}
storedRoleInfo = cmd.ExternalUser.OrgRoles
return nil
})
sc.fakeReq("GET", "/")
sc.req.Header.Set(sc.cfg.AuthProxyHeaderName, hdrName)
sc.req.Header.Set("X-WEBAUTH-ROLE", orgRole)
sc.exec()
assert.True(t, sc.context.IsSignedIn)
assert.Equal(t, userID, sc.context.UserId)
assert.Equal(t, defaultOrgId, sc.context.OrgId)
assert.Equal(t, orgRole, string(sc.context.OrgRole))
}, func(cfg *setting.Cfg) {
configure(cfg)
cfg.LDAPEnabled = false
cfg.AuthProxyAutoSignUp = true
})
middlewareScenario(t, "Should NOT assign role from header to non-default org", func(t *testing.T, sc *scenarioContext) {
var storedRoleInfo map[int64]models.RoleType = nil
bus.AddHandlerCtx("test", func(ctx context.Context, query *models.GetSignedInUserQuery) error {
if query.UserId > 0 {
query.Result = &models.SignedInUser{OrgId: orgID, UserId: userID, OrgRole: storedRoleInfo[orgID]}
return nil
}
return models.ErrUserNotFound
})
bus.AddHandler("test", func(cmd *models.UpsertUserCommand) error {
cmd.Result = &models.User{Id: userID}
storedRoleInfo = cmd.ExternalUser.OrgRoles
return nil
})
sc.fakeReq("GET", "/")
sc.req.Header.Set(sc.cfg.AuthProxyHeaderName, hdrName)
sc.req.Header.Set("X-WEBAUTH-ROLE", "Admin")
sc.req.Header.Set("X-Grafana-Org-Id", strconv.FormatInt(orgID, 10))
sc.exec()
assert.True(t, sc.context.IsSignedIn)
assert.Equal(t, userID, sc.context.UserId)
assert.Equal(t, orgID, sc.context.OrgId)
// For non-default org, the user role should be empty
assert.Equal(t, "", string(sc.context.OrgRole))
}, func(cfg *setting.Cfg) {
configure(cfg)
cfg.LDAPEnabled = false
cfg.AuthProxyAutoSignUp = true
})
middlewareScenario(t, "Should get an existing user from header", func(t *testing.T, sc *scenarioContext) {
const userID int64 = 12
const orgID int64 = 2

View File

@@ -363,8 +363,9 @@ type DashboardProvisioning struct {
}
type DeleteDashboardCommand struct {
Id int64
OrgId int64
Id int64
OrgId int64
ForceDeleteFolderRules bool
}
type DeleteOrphanedProvisionedDashboardsCommand struct {

View File

@@ -11,6 +11,7 @@ import (
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/httpclient"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials"
)
func (ds *DataSource) getTimeout() time.Duration {
@@ -66,10 +67,14 @@ func (ds *DataSource) GetHTTPTransport(provider httpclient.Provider, customMiddl
return t.roundTripper, nil
}
opts := ds.HTTPClientOptions()
opts, err := ds.HTTPClientOptions()
if err != nil {
return nil, err
}
opts.Middlewares = customMiddlewares
rt, err := provider.GetTransport(opts)
rt, err := provider.GetTransport(*opts)
if err != nil {
return nil, err
}
@@ -82,7 +87,7 @@ func (ds *DataSource) GetHTTPTransport(provider httpclient.Provider, customMiddl
return rt, nil
}
func (ds *DataSource) HTTPClientOptions() sdkhttpclient.Options {
func (ds *DataSource) HTTPClientOptions() (*sdkhttpclient.Options, error) {
tlsOptions := ds.TLSOptions()
timeouts := &sdkhttpclient.TimeoutOptions{
Timeout: ds.getTimeout(),
@@ -95,7 +100,7 @@ func (ds *DataSource) HTTPClientOptions() sdkhttpclient.Options {
MaxIdleConnsPerHost: sdkhttpclient.DefaultTimeoutOptions.MaxIdleConnsPerHost,
IdleConnTimeout: sdkhttpclient.DefaultTimeoutOptions.IdleConnTimeout,
}
opts := sdkhttpclient.Options{
opts := &sdkhttpclient.Options{
Timeouts: timeouts,
Headers: getCustomHeaders(ds.JsonData, ds.DecryptedValues()),
Labels: map[string]string{
@@ -121,6 +126,19 @@ func (ds *DataSource) HTTPClientOptions() sdkhttpclient.Options {
}
}
if ds.JsonData != nil && ds.JsonData.Get("azureAuth").MustBool() {
credentials, err := azcredentials.FromDatasourceData(ds.JsonData.MustMap(), ds.DecryptedValues())
if err != nil {
err = fmt.Errorf("invalid Azure credentials: %s", err)
return nil, err
}
opts.CustomOptions["_azureAuth"] = true
if credentials != nil {
opts.CustomOptions["_azureCredentials"] = credentials
}
}
if ds.JsonData != nil && ds.JsonData.Get("sigV4Auth").MustBool(false) {
opts.SigV4 = &sdkhttpclient.SigV4Config{
Service: awsServiceNamespace(ds.Type),
@@ -140,7 +158,7 @@ func (ds *DataSource) HTTPClientOptions() sdkhttpclient.Options {
}
}
return opts
return opts, nil
}
func (ds *DataSource) TLSOptions() sdkhttpclient.TLSOptions {
@@ -180,7 +198,11 @@ func (ds *DataSource) TLSOptions() sdkhttpclient.TLSOptions {
}
func (ds *DataSource) GetTLSConfig(httpClientProvider httpclient.Provider) (*tls.Config, error) {
return httpClientProvider.GetTLSConfig(ds.HTTPClientOptions())
opts, err := ds.HTTPClientOptions()
if err != nil {
return nil, err
}
return httpClientProvider.GetTLSConfig(*opts)
}
// getCustomHeaders returns a map with all the to be set headers

View File

@@ -13,6 +13,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/httpclient"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials"
"github.com/grafana/grafana/pkg/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -394,6 +395,109 @@ func TestDataSource_DecryptedValue(t *testing.T) {
})
}
func TestDataSource_HTTPClientOptions(t *testing.T) {
emptyJsonData := simplejson.New()
emptySecureJsonData := map[string][]byte{}
ds := DataSource{
Id: 1,
Url: "https://api.example.com",
Type: "prometheus",
}
t.Run("Azure authentication", func(t *testing.T) {
t.Run("should be disabled if not enabled in JsonData", func(t *testing.T) {
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
opts, err := ds.HTTPClientOptions()
require.NoError(t, err)
assert.NotEqual(t, true, opts.CustomOptions["_azureAuth"])
assert.NotContains(t, opts.CustomOptions, "_azureCredentials")
})
t.Run("should be enabled if enabled in JsonData without credentials configured", func(t *testing.T) {
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
"azureAuth": true,
})
opts, err := ds.HTTPClientOptions()
require.NoError(t, err)
assert.Equal(t, true, opts.CustomOptions["_azureAuth"])
assert.NotContains(t, opts.CustomOptions, "_azureCredentials")
})
t.Run("should be enabled if enabled in JsonData with credentials configured", func(t *testing.T) {
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
"azureAuth": true,
"azureCredentials": map[string]interface{}{
"authType": "msi",
},
})
opts, err := ds.HTTPClientOptions()
require.NoError(t, err)
assert.Equal(t, true, opts.CustomOptions["_azureAuth"])
require.Contains(t, opts.CustomOptions, "_azureCredentials")
credentials := opts.CustomOptions["_azureCredentials"]
assert.IsType(t, &azcredentials.AzureManagedIdentityCredentials{}, credentials)
})
t.Run("should be disabled if disabled in JsonData even with credentials configured", func(t *testing.T) {
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
"azureAuth": false,
"azureCredentials": map[string]interface{}{
"authType": "msi",
},
})
opts, err := ds.HTTPClientOptions()
require.NoError(t, err)
assert.NotEqual(t, true, opts.CustomOptions["_azureAuth"])
assert.NotContains(t, opts.CustomOptions, "_azureCredentials")
})
t.Run("should fail if credentials are invalid", func(t *testing.T) {
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
"azureAuth": true,
"azureCredentials": "invalid",
})
_, err := ds.HTTPClientOptions()
assert.Error(t, err)
})
t.Run("should pass resourceId from JsonData", func(t *testing.T) {
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
"azureEndpointResourceId": "https://api.example.com/abd5c4ce-ca73-41e9-9cb2-bed39aa2adb5",
})
opts, err := ds.HTTPClientOptions()
require.NoError(t, err)
require.Contains(t, opts.CustomOptions, "azureEndpointResourceId")
azureEndpointResourceId := opts.CustomOptions["azureEndpointResourceId"]
assert.Equal(t, "https://api.example.com/abd5c4ce-ca73-41e9-9cb2-bed39aa2adb5", azureEndpointResourceId)
})
})
}
func clearDSProxyCache(t *testing.T) {
t.Helper()

View File

@@ -15,6 +15,7 @@ var (
ErrFolderSameNameExists = errors.New("a folder or dashboard in the general folder with the same name already exists")
ErrFolderFailedGenerateUniqueUid = errors.New("failed to generate unique folder ID")
ErrFolderAccessDenied = errors.New("access denied to folder")
ErrFolderContainsAlertRules = errors.New("folder contains alert rules")
)
type Folder struct {

View File

@@ -45,7 +45,7 @@ var isLDAPEnabled = func(cfg *setting.Cfg) bool {
var newLDAP = multildap.New
// supportedHeaders states the supported headers configuration fields
var supportedHeaderFields = []string{"Name", "Email", "Login", "Groups"}
var supportedHeaderFields = []string{"Name", "Email", "Login", "Groups", "Role"}
// AuthProxy struct
type AuthProxy struct {
@@ -152,7 +152,7 @@ func HashCacheKey(key string) (string, error) {
// getKey forms a key for the cache based on the headers received as part of the authentication flow.
// Our configuration supports multiple headers. The main header contains the email or username.
// And the additional ones that allow us to specify extra attributes: Name, Email or Groups.
// And the additional ones that allow us to specify extra attributes: Name, Email, Role, or Groups.
func (auth *AuthProxy) getKey() (string, error) {
key := strings.TrimSpace(auth.header) // start the key with the main header
@@ -278,9 +278,23 @@ func (auth *AuthProxy) LoginViaHeader() (int64, error) {
}
auth.headersIterator(func(field string, header string) {
if field == "Groups" {
switch field {
case "Groups":
extUser.Groups = util.SplitString(header)
} else {
case "Role":
// If Role header is specified, we update the user role of the default org
if header != "" {
rt := models.RoleType(header)
if rt.IsValid() {
extUser.OrgRoles = map[int64]models.RoleType{}
orgID := int64(1)
if setting.AutoAssignOrg && setting.AutoAssignOrgId > 0 {
orgID = int64(setting.AutoAssignOrgId)
}
extUser.OrgRoles[orgID] = rt
}
}
default:
reflect.ValueOf(extUser).Elem().FieldByName(field).SetString(header)
}
})

View File

@@ -107,8 +107,9 @@ func TestMiddlewareContext(t *testing.T) {
t.Run("When the cache key contains additional headers", func(t *testing.T) {
const id int64 = 33
const group = "grafana-core-team"
const role = "Admin"
h, err := HashCacheKey(hdrName + "-" + group)
h, err := HashCacheKey(hdrName + "-" + group + "-" + role)
require.NoError(t, err)
key := fmt.Sprintf(CachePrefix, h)
err = cache.Set(key, id, 0)
@@ -116,9 +117,10 @@ func TestMiddlewareContext(t *testing.T) {
auth := prepareMiddleware(t, cache, func(req *http.Request, cfg *setting.Cfg) {
req.Header.Set("X-WEBAUTH-GROUPS", group)
cfg.AuthProxyHeaders = map[string]string{"Groups": "X-WEBAUTH-GROUPS"}
req.Header.Set("X-WEBAUTH-ROLE", role)
cfg.AuthProxyHeaders = map[string]string{"Groups": "X-WEBAUTH-GROUPS", "Role": "X-WEBAUTH-ROLE"}
})
assert.Equal(t, "auth-proxy-sync-ttl:14f69b7023baa0ac98c96b31cec07bc0", key)
assert.Equal(t, "auth-proxy-sync-ttl:f5acfffd56daac98d502ef8c8b8c5d56", key)
gotID, err := auth.Login(logger, false)
require.NoError(t, err)

View File

@@ -19,7 +19,7 @@ type FolderService interface {
GetFolderByTitle(title string) (*models.Folder, error)
CreateFolder(title, uid string) (*models.Folder, error)
UpdateFolder(uid string, cmd *models.UpdateFolderCommand) error
DeleteFolder(uid string) (*models.Folder, error)
DeleteFolder(uid string, forceDeleteRules bool) (*models.Folder, error)
MakeUserAdmin(orgID int64, userID, folderID int64, setViewAndEditPermissions bool) error
}
@@ -192,7 +192,7 @@ func (dr *dashboardServiceImpl) UpdateFolder(existingUid string, cmd *models.Upd
return nil
}
func (dr *dashboardServiceImpl) DeleteFolder(uid string) (*models.Folder, error) {
func (dr *dashboardServiceImpl) DeleteFolder(uid string, forceDeleteRules bool) (*models.Folder, error) {
query := models.GetDashboardQuery{OrgId: dr.orgId, Uid: uid}
dashFolder, err := getFolder(query)
if err != nil {
@@ -207,7 +207,7 @@ func (dr *dashboardServiceImpl) DeleteFolder(uid string) (*models.Folder, error)
return nil, models.ErrFolderAccessDenied
}
deleteCmd := models.DeleteDashboardCommand{OrgId: dr.orgId, Id: dashFolder.Id}
deleteCmd := models.DeleteDashboardCommand{OrgId: dr.orgId, Id: dashFolder.Id, ForceDeleteFolderRules: forceDeleteRules}
if err := bus.Dispatch(&deleteCmd); err != nil {
return nil, toFolderError(err)
}

View File

@@ -67,7 +67,7 @@ func TestFolderService(t *testing.T) {
})
t.Run("When deleting folder by uid should return access denied error", func(t *testing.T) {
_, err := service.DeleteFolder("uid")
_, err := service.DeleteFolder("uid", false)
require.Error(t, err)
require.Equal(t, err, models.ErrFolderAccessDenied)
})
@@ -121,7 +121,7 @@ func TestFolderService(t *testing.T) {
})
t.Run("When deleting folder by uid should not return access denied error", func(t *testing.T) {
_, err := service.DeleteFolder("uid")
_, err := service.DeleteFolder("uid", false)
require.NoError(t, err)
})

View File

@@ -39,7 +39,7 @@ func (c *cache) getOrCreate(alertRule *ngModels.AlertRule, result eval.Result) *
// clone the labels so we don't change eval.Result
labels := result.Instance.Copy()
attachRuleLabels(labels, alertRule)
ruleLabels, annotations := c.expandRuleLabelsAndAnnotations(alertRule, labels, result.Values)
ruleLabels, annotations := c.expandRuleLabelsAndAnnotations(alertRule, labels, result)
// if duplicate labels exist, alertRule label will take precedence
lbs := mergeLabels(ruleLabels, result.Instance)
@@ -88,11 +88,11 @@ func attachRuleLabels(m map[string]string, alertRule *ngModels.AlertRule) {
m[prometheusModel.AlertNameLabel] = alertRule.Title
}
func (c *cache) expandRuleLabelsAndAnnotations(alertRule *ngModels.AlertRule, labels map[string]string, values map[string]eval.NumberValueCapture) (map[string]string, map[string]string) {
func (c *cache) expandRuleLabelsAndAnnotations(alertRule *ngModels.AlertRule, labels map[string]string, alertInstance eval.Result) (map[string]string, map[string]string) {
expand := func(original map[string]string) map[string]string {
expanded := make(map[string]string, len(original))
for k, v := range original {
ev, err := expandTemplate(alertRule.Title, v, labels, values)
ev, err := expandTemplate(alertRule.Title, v, labels, alertInstance)
expanded[k] = ev
if err != nil {
c.log.Error("error in expanding template", "name", k, "value", v, "err", err.Error())
@@ -122,9 +122,9 @@ func (v templateCaptureValue) String() string {
return "null"
}
func expandTemplate(name, text string, labels map[string]string, values map[string]eval.NumberValueCapture) (result string, resultErr error) {
func expandTemplate(name, text string, labels map[string]string, alertInstance eval.Result) (result string, resultErr error) {
name = "__alert_" + name
text = "{{- $labels := .Labels -}}{{- $values := .Values -}}" + text
text = "{{- $labels := .Labels -}}{{- $values := .Values -}}{{- $value := .Value -}}" + text
// It'd better to have no alert description than to kill the whole process
// if there's a bug in the template.
defer func() {
@@ -145,11 +145,12 @@ func expandTemplate(name, text string, labels map[string]string, values map[stri
if err := tmpl.Execute(&buffer, struct {
Labels map[string]string
Values map[string]templateCaptureValue
Value string
}{
Labels: labels,
Values: func() map[string]templateCaptureValue {
m := make(map[string]templateCaptureValue)
for k, v := range values {
for k, v := range alertInstance.Values {
m[k] = templateCaptureValue{
Labels: v.Labels,
Value: v.Value,
@@ -157,6 +158,7 @@ func expandTemplate(name, text string, labels map[string]string, values map[stri
}
return m
}(),
Value: alertInstance.EvaluationString,
}); err != nil {
return "", fmt.Errorf("error executing template %v: %s", name, err.Error())
}

View File

@@ -1,9 +1,13 @@
package state
import (
"errors"
"testing"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/services/ngalert/eval"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
ptr "github.com/xorcare/pointer"
)
@@ -32,3 +36,65 @@ func TestTemplateCaptureValueStringer(t *testing.T) {
})
}
}
func TestExpandTemplate(t *testing.T) {
cases := []struct {
name string
text string
alertInstance eval.Result
labels data.Labels
expected string
expectedError error
}{{
name: "instance labels are expanded into $labels",
text: "{{ $labels.instance }} is down",
labels: data.Labels{"instance": "foo"},
expected: "foo is down",
}, {
name: "missing instance label returns error",
text: "{{ $labels.instance }} is down",
labels: data.Labels{},
expectedError: errors.New("error executing template __alert_test: template: __alert_test:1:86: executing \"__alert_test\" at <$labels.instance>: map has no entry for key \"instance\""),
}, {
name: "values are expanded into $values",
text: "{{ $values.A.Labels.instance }} has value {{ $values.A }}",
alertInstance: eval.Result{
Values: map[string]eval.NumberValueCapture{
"A": {
Var: "A",
Labels: data.Labels{"instance": "foo"},
Value: ptr.Float64(10),
},
},
},
expected: "foo has value 10",
}, {
name: "missing label in $values returns error",
text: "{{ $values.A.Labels.instance }} has value {{ $values.A }}",
alertInstance: eval.Result{
Values: map[string]eval.NumberValueCapture{
"A": {
Var: "A",
Labels: data.Labels{},
Value: ptr.Float64(10),
},
},
},
expectedError: errors.New("error executing template __alert_test: template: __alert_test:1:86: executing \"__alert_test\" at <$values.A.Labels.instance>: map has no entry for key \"instance\""),
}, {
name: "value string is expanded into $value",
text: "{{ $value }}",
alertInstance: eval.Result{
EvaluationString: "[ var='A' labels={instance=foo} value=10 ]",
},
expected: "[ var='A' labels={instance=foo} value=10 ]",
}}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
v, err := expandTemplate("test", c.text, c.labels, c.alertInstance)
require.Equal(t, c.expectedError, err)
require.Equal(t, c.expected, v)
})
}
}

View File

@@ -476,16 +476,27 @@ func deleteDashboard(cmd *models.DeleteDashboardCommand, sess *DBSession) error
}
}
// clean ngalert tables
ngalertDeletes := []string{
"DELETE FROM alert_rule WHERE namespace_uid = (SELECT uid FROM dashboard WHERE id = ?)",
"DELETE FROM alert_rule_version WHERE rule_namespace_uid = (SELECT uid FROM dashboard WHERE id = ?)",
var existingRuleID int64
exists, err := sess.Table("alert_rule").Where("namespace_uid = (SELECT uid FROM dashboard WHERE id = ?)", dashboard.Id).Cols("id").Get(&existingRuleID)
if err != nil {
return err
}
if exists {
if !cmd.ForceDeleteFolderRules {
return fmt.Errorf("folder cannot be deleted: %w", models.ErrFolderContainsAlertRules)
}
for _, sql := range ngalertDeletes {
_, err := sess.Exec(sql, dashboard.Id)
if err != nil {
return err
// Delete all rules under this folder.
deleteNGAlertsByFolder := []string{
"DELETE FROM alert_rule WHERE namespace_uid = (SELECT uid FROM dashboard WHERE id = ?)",
"DELETE FROM alert_rule_version WHERE rule_namespace_uid = (SELECT uid FROM dashboard WHERE id = ?)",
}
for _, sql := range deleteNGAlertsByFolder {
_, err := sess.Exec(sql, dashboard.Id)
if err != nil {
return err
}
}
}
}

View File

@@ -4,6 +4,8 @@ package sqlstore
import (
"context"
"encoding/json"
"errors"
"fmt"
"testing"
"time"
@@ -29,6 +31,7 @@ func TestDashboardDataAccess(t *testing.T) {
savedDash := insertTestDashboard(t, sqlStore, "test dash 23", 1, savedFolder.Id, false, "prod", "webapp")
insertTestDashboard(t, sqlStore, "test dash 45", 1, savedFolder.Id, false, "prod")
savedDash2 := insertTestDashboard(t, sqlStore, "test dash 67", 1, 0, false, "prod")
insertTestRule(t, sqlStore, savedFolder.OrgId, savedFolder.Uid)
Convey("Should return dashboard model", func() {
So(savedDash.Title, ShouldEqual, "test dash 23")
@@ -204,8 +207,14 @@ func TestDashboardDataAccess(t *testing.T) {
So(err, ShouldBeNil)
})
Convey("Should be able to delete a dashboard folder and its children", func() {
deleteCmd := &models.DeleteDashboardCommand{Id: savedFolder.Id}
Convey("Should be not able to delete a dashboard if force delete rules is disabled", func() {
deleteCmd := &models.DeleteDashboardCommand{Id: savedFolder.Id, ForceDeleteFolderRules: false}
err := DeleteDashboard(deleteCmd)
So(errors.Is(err, models.ErrFolderContainsAlertRules), ShouldBeTrue)
})
Convey("Should be able to delete a dashboard folder and its children if force delete rules is enabled", func() {
deleteCmd := &models.DeleteDashboardCommand{Id: savedFolder.Id, ForceDeleteFolderRules: true}
err := DeleteDashboard(deleteCmd)
So(err, ShouldBeNil)
@@ -219,6 +228,20 @@ func TestDashboardDataAccess(t *testing.T) {
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 0)
sqlStore.WithDbSession(context.Background(), func(sess *DBSession) error {
var existingRuleID int64
exists, err := sess.Table("alert_rule").Where("namespace_uid = (SELECT uid FROM dashboard WHERE id = ?)", savedFolder.Id).Cols("id").Get(&existingRuleID)
require.NoError(t, err)
So(exists, ShouldBeFalse)
var existingRuleVersionID int64
exists, err = sess.Table("alert_rule_version").Where("rule_namespace_uid = (SELECT uid FROM dashboard WHERE id = ?)", savedFolder.Id).Cols("id").Get(&existingRuleVersionID)
require.NoError(t, err)
So(exists, ShouldBeFalse)
return nil
})
})
Convey("Should return error if no dashboard is found for update when dashboard id is greater than zero", func() {
@@ -460,6 +483,84 @@ func insertTestDashboard(t *testing.T, sqlStore *SQLStore, title string, orgId i
return dash
}
func insertTestRule(t *testing.T, sqlStore *SQLStore, foderOrgID int64, folderUID string) {
sqlStore.WithDbSession(context.Background(), func(sess *DBSession) error {
type alertQuery struct {
RefID string
DatasourceUID string
Model json.RawMessage
}
type alertRule struct {
ID int64 `xorm:"pk autoincr 'id'"`
OrgID int64 `xorm:"org_id"`
Title string
Updated time.Time
UID string `xorm:"uid"`
NamespaceUID string `xorm:"namespace_uid"`
RuleGroup string
Condition string
Data []alertQuery
}
rule := alertRule{
OrgID: foderOrgID,
NamespaceUID: folderUID,
UID: "rule",
RuleGroup: "rulegroup",
Updated: time.Now(),
Condition: "A",
Data: []alertQuery{
{
RefID: "A",
DatasourceUID: "-100",
Model: json.RawMessage(`{
"type": "math",
"expression": "2 + 3 > 1"
}`),
},
},
}
_, err := sess.Insert(&rule)
require.NoError(t, err)
type alertRuleVersion struct {
ID int64 `xorm:"pk autoincr 'id'"`
RuleOrgID int64 `xorm:"rule_org_id"`
RuleUID string `xorm:"rule_uid"`
RuleNamespaceUID string `xorm:"rule_namespace_uid"`
RuleGroup string
ParentVersion int64
RestoredFrom int64
Version int64
Created time.Time
Title string
Condition string
Data []alertQuery
IntervalSeconds int64
}
ruleVersion := alertRuleVersion{
RuleOrgID: rule.OrgID,
RuleUID: rule.UID,
RuleNamespaceUID: rule.NamespaceUID,
RuleGroup: rule.RuleGroup,
Created: rule.Updated,
Condition: rule.Condition,
Data: rule.Data,
ParentVersion: 0,
RestoredFrom: 0,
Version: 1,
IntervalSeconds: 60,
}
_, err = sess.Insert(&ruleVersion)
require.NoError(t, err)
return err
})
}
func insertTestDashboardForPlugin(t *testing.T, sqlStore *SQLStore, title string, orgId int64,
folderId int64, isFolder bool, pluginId string) *models.Dashboard {
t.Helper()

View File

@@ -54,6 +54,16 @@ func TestLoadingSettings(t *testing.T) {
}
})
Convey("sample.ini should load successfully", func() {
customInitPath := CustomInitPath
CustomInitPath = "conf/sample.ini"
cfg := NewCfg()
err := cfg.Load(&CommandLineArgs{HomePath: "../../"})
So(err, ShouldBeNil)
// Restore CustomInitPath to avoid side effects.
CustomInitPath = customInitPath
})
Convey("Should be able to override via environment variables", func() {
err := os.Setenv("GF_SECURITY_ADMIN_USER", "superduper")
require.NoError(t, err)

View File

@@ -748,7 +748,7 @@ func TestDeleteFolderWithRules(t *testing.T) {
assert.JSONEq(t, expectedGetRulesResponseBody, string(b))
}
// Next, the editor can delete the folder.
// Next, the editor can not delete the folder because it contains Grafana 8 alerts.
{
u := fmt.Sprintf("http://editor:editor@%s/api/folders/%s", grafanaListedAddr, namespaceUID)
req, err := http.NewRequest(http.MethodDelete, u, nil)
@@ -762,6 +762,24 @@ func TestDeleteFolderWithRules(t *testing.T) {
})
b, err := ioutil.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
require.JSONEq(t, `{"message":"folder cannot be deleted: folder contains alert rules"}`, string(b))
}
// Next, the editor can delete the folder if forceDeleteRules is true.
{
u := fmt.Sprintf("http://editor:editor@%s/api/folders/%s?forceDeleteRules=true", grafanaListedAddr, namespaceUID)
req, err := http.NewRequest(http.MethodDelete, u, nil)
require.NoError(t, err)
client := &http.Client{}
resp, err := client.Do(req)
require.NoError(t, err)
t.Cleanup(func() {
err := resp.Body.Close()
require.NoError(t, err)
})
b, err := ioutil.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, 200, resp.StatusCode)
require.JSONEq(t, `{"id":1,"message":"Folder default deleted","title":"default"}`, string(b))
}

View File

@@ -0,0 +1,83 @@
package azcredentials
import (
"fmt"
)
func FromDatasourceData(data map[string]interface{}, secureData map[string]string) (AzureCredentials, error) {
if credentialsObj, err := getMapOptional(data, "azureCredentials"); err != nil {
return nil, err
} else if credentialsObj == nil {
return nil, nil
} else {
return getFromCredentialsObject(credentialsObj, secureData)
}
}
func getFromCredentialsObject(credentialsObj map[string]interface{}, secureData map[string]string) (AzureCredentials, error) {
authType, err := getStringValue(credentialsObj, "authType")
if err != nil {
return nil, err
}
switch authType {
case AzureAuthManagedIdentity:
credentials := &AzureManagedIdentityCredentials{}
return credentials, nil
case AzureAuthClientSecret:
cloud, err := getStringValue(credentialsObj, "azureCloud")
if err != nil {
return nil, err
}
tenantId, err := getStringValue(credentialsObj, "tenantId")
if err != nil {
return nil, err
}
clientId, err := getStringValue(credentialsObj, "clientId")
if err != nil {
return nil, err
}
clientSecret := secureData["azureClientSecret"]
credentials := &AzureClientSecretCredentials{
AzureCloud: cloud,
TenantId: tenantId,
ClientId: clientId,
ClientSecret: clientSecret,
}
return credentials, nil
default:
err := fmt.Errorf("the authentication type '%s' not supported", authType)
return nil, err
}
}
func getMapOptional(obj map[string]interface{}, key string) (map[string]interface{}, error) {
if untypedValue, ok := obj[key]; ok {
if value, ok := untypedValue.(map[string]interface{}); ok {
return value, nil
} else {
err := fmt.Errorf("the field '%s' should be an object", key)
return nil, err
}
} else {
// Value optional, not error
return nil, nil
}
}
func getStringValue(obj map[string]interface{}, key string) (string, error) {
if untypedValue, ok := obj[key]; ok {
if value, ok := untypedValue.(string); ok {
return value, nil
} else {
err := fmt.Errorf("the field '%s' should be a string", key)
return "", err
}
} else {
err := fmt.Errorf("the field '%s' should be set", key)
return "", err
}
}

View File

@@ -11,13 +11,17 @@ const authenticationMiddlewareName = "AzureAuthentication"
func AuthMiddleware(tokenProvider AzureTokenProvider, scopes []string) httpclient.Middleware {
return httpclient.NamedMiddlewareFunc(authenticationMiddlewareName, func(opts httpclient.Options, next http.RoundTripper) http.RoundTripper {
return httpclient.RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
token, err := tokenProvider.GetAccessToken(req.Context(), scopes)
if err != nil {
return nil, fmt.Errorf("failed to retrieve Azure access token: %w", err)
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
return next.RoundTrip(req)
})
return ApplyAuth(tokenProvider, scopes, next)
})
}
func ApplyAuth(tokenProvider AzureTokenProvider, scopes []string, next http.RoundTripper) http.RoundTripper {
return httpclient.RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
token, err := tokenProvider.GetAccessToken(req.Context(), scopes)
if err != nil {
return nil, fmt.Errorf("failed to retrieve Azure access token: %w", err)
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
return next.RoundTrip(req)
})
}

View File

@@ -1,6 +1,6 @@
{
"name": "@grafana-plugins/input-datasource",
"version": "8.1.0-pre",
"version": "8.1.0-beta.2",
"description": "Input Datasource",
"private": true,
"repository": {
@@ -15,9 +15,9 @@
},
"author": "Grafana Labs",
"devDependencies": {
"@grafana/data": "8.1.0-pre",
"@grafana/toolkit": "8.1.0-pre",
"@grafana/ui": "8.1.0-pre"
"@grafana/data": "8.1.0-beta.2",
"@grafana/toolkit": "8.1.0-beta.2",
"@grafana/ui": "8.1.0-beta.2"
},
"volta": {
"extends": "../../../package.json"

View File

@@ -59,7 +59,9 @@ describe('createSpanLinkFactory', () => {
} as any);
expect(linkDef.href).toBe(
`/explore?left={"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{cluster=\\"cluster1\\", hostname=\\"hostname1\\"}","refId":""}]}`
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{cluster=\\"cluster1\\", hostname=\\"hostname1\\"}","refId":""}]}'
)}`
);
});
@@ -91,7 +93,9 @@ describe('createSpanLinkFactory', () => {
} as any);
expect(linkDef.href).toBe(
`/explore?left={"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{ip=\\"192.168.0.1\\"}","refId":""}]}`
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{ip=\\"192.168.0.1\\"}","refId":""}]}'
)}`
);
});
@@ -126,7 +130,9 @@ describe('createSpanLinkFactory', () => {
} as any);
expect(linkDef.href).toBe(
`/explore?left={"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{ip=\\"192.168.0.1\\", host=\\"host\\"}","refId":""}]}`
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:00:00.000Z","to":"2020-10-14T01:00:01.000Z"},"datasource":"loki1","queries":[{"expr":"{ip=\\"192.168.0.1\\", host=\\"host\\"}","refId":""}]}'
)}`
);
});
@@ -163,7 +169,9 @@ describe('createSpanLinkFactory', () => {
} as any);
expect(linkDef.href).toBe(
`/explore?left={"range":{"from":"2020-10-14T01:01:00.000Z","to":"2020-10-14T01:01:01.000Z"},"datasource":"loki1","queries":[{"expr":"{hostname=\\"hostname1\\"}","refId":""}]}`
`/explore?left=${encodeURIComponent(
'{"range":{"from":"2020-10-14T01:01:00.000Z","to":"2020-10-14T01:01:01.000Z"},"datasource":"loki1","queries":[{"expr":"{hostname=\\"hostname1\\"}","refId":""}]}'
)}`
);
});
});

View File

@@ -49,7 +49,9 @@ describe('getFieldLinksForExplore', () => {
const links = getFieldLinksForExplore({ field, rowIndex: 0, splitOpenFn: splitfn, range });
expect(links[0].href).toBe(
'/explore?left={"range":{"from":"now-1h","to":"now"},"datasource":"test_ds","queries":[{"query":"query_1"}]}'
`/explore?left=${encodeURIComponent(
'{"range":{"from":"now-1h","to":"now"},"datasource":"test_ds","queries":[{"query":"query_1"}]}'
)}`
);
expect(links[0].title).toBe('test_ds');

View File

@@ -66,10 +66,11 @@ export class FolderSettingsPage extends PureComponent<Props, State> {
evt.stopPropagation();
evt.preventDefault();
const confirmationText = `Do you want to delete this folder and all its dashboards and alerts?`;
appEvents.publish(
new ShowConfirmModalEvent({
title: 'Delete',
text: `Do you want to delete this folder and all its dashboards?`,
text: confirmationText,
icon: 'trash-alt',
yesText: 'Delete',
onConfirm: () => {

View File

@@ -31,7 +31,7 @@ export function saveFolder(folder: FolderState): ThunkResult<void> {
export function deleteFolder(uid: string): ThunkResult<void> {
return async (dispatch) => {
await backendSrv.delete(`/api/folders/${uid}`);
await backendSrv.delete(`/api/folders/${uid}?forceDeleteRules=true`);
locationService.push('/dashboards');
};
}

View File

@@ -213,7 +213,7 @@ export function saveDashboard(options: SaveDashboardOptions) {
function deleteFolder(uid: string, showSuccessAlert: boolean) {
return getBackendSrv().request({
method: 'DELETE',
url: `/api/folders/${uid}`,
url: `/api/folders/${uid}?forceDeleteRules=true`,
showSuccessAlert: showSuccessAlert === true,
});
}

View File

@@ -30,9 +30,9 @@ export const ConfirmDeleteModal: FC<Props> = ({ results, onDeleteItems, isOpen,
if (folderCount > 0 && dashCount > 0) {
text += `selected folder${folderEnding} and dashboard${dashEnding}?\n`;
subtitle = `All dashboards of the selected folder${folderEnding} will also be deleted`;
subtitle = `All dashboards and alerts of the selected folder${folderEnding} will also be deleted`;
} else if (folderCount > 0) {
text += `selected folder${folderEnding} and all its dashboards?`;
text += `selected folder${folderEnding} and all their dashboards and alerts?`;
} else {
text += `selected dashboard${dashEnding}?`;
}

View File

@@ -0,0 +1,51 @@
import React, { FunctionComponent, useMemo } from 'react';
import { InlineFormLabel, Input } from '@grafana/ui';
import { config } from '@grafana/runtime';
import { KnownAzureClouds, AzureCredentials } from './AzureCredentials';
import { getCredentials, updateCredentials } from './AzureCredentialsConfig';
import { AzureCredentialsForm } from './AzureCredentialsForm';
import { HttpSettingsBaseProps } from '@grafana/ui/src/components/DataSourceSettings/types';
export const AzureAuthSettings: FunctionComponent<HttpSettingsBaseProps> = (props: HttpSettingsBaseProps) => {
const { dataSourceConfig, onChange } = props;
const credentials = useMemo(() => getCredentials(dataSourceConfig), [dataSourceConfig]);
const onCredentialsChange = (credentials: AzureCredentials): void => {
onChange(updateCredentials(dataSourceConfig, credentials));
};
return (
<>
<h6>Azure Authentication</h6>
<AzureCredentialsForm
managedIdentityEnabled={config.azure.managedIdentityEnabled}
credentials={credentials}
azureCloudOptions={KnownAzureClouds}
onCredentialsChange={onCredentialsChange}
/>
<h6>Azure Configuration</h6>
<div className="gf-form-group">
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel className="width-12">AAD resource ID</InlineFormLabel>
<div className="width-15">
<Input
className="width-30"
value={dataSourceConfig.jsonData.azureEndpointResourceId || ''}
onChange={(event) =>
onChange({
...dataSourceConfig,
jsonData: { ...dataSourceConfig.jsonData, azureEndpointResourceId: event.currentTarget.value },
})
}
/>
</div>
</div>
</div>
</div>
</>
);
};
export default AzureAuthSettings;

View File

@@ -0,0 +1,48 @@
import { SelectableValue } from '@grafana/data';
export enum AzureCloud {
Public = 'AzureCloud',
China = 'AzureChinaCloud',
USGovernment = 'AzureUSGovernment',
Germany = 'AzureGermanCloud',
None = '',
}
export const KnownAzureClouds = [
{ value: AzureCloud.Public, label: 'Azure' },
{ value: AzureCloud.China, label: 'Azure China' },
{ value: AzureCloud.USGovernment, label: 'Azure US Government' },
{ value: AzureCloud.Germany, label: 'Azure Germany' },
] as SelectableValue[];
export type AzureAuthType = 'msi' | 'clientsecret';
export type ConcealedSecret = symbol;
interface AzureCredentialsBase {
authType: AzureAuthType;
defaultSubscriptionId?: string;
}
export interface AzureManagedIdentityCredentials extends AzureCredentialsBase {
authType: 'msi';
}
export interface AzureClientSecretCredentials extends AzureCredentialsBase {
authType: 'clientsecret';
azureCloud?: string;
tenantId?: string;
clientId?: string;
clientSecret?: string | ConcealedSecret;
}
export type AzureCredentials = AzureManagedIdentityCredentials | AzureClientSecretCredentials;
export function isCredentialsComplete(credentials: AzureCredentials): boolean {
switch (credentials.authType) {
case 'msi':
return true;
case 'clientsecret':
return !!(credentials.azureCloud && credentials.tenantId && credentials.clientId && credentials.clientSecret);
}
}

View File

@@ -0,0 +1,107 @@
import { DataSourceSettings } from '@grafana/data';
import { config } from '@grafana/runtime';
import { AzureCloud, AzureCredentials, ConcealedSecret } from './AzureCredentials';
const concealed: ConcealedSecret = Symbol('Concealed client secret');
function getDefaultAzureCloud(): string {
return config.azure.cloud || AzureCloud.Public;
}
function getSecret(options: DataSourceSettings<any, any>): undefined | string | ConcealedSecret {
if (options.secureJsonFields.azureClientSecret) {
// The secret is concealed on server
return concealed;
} else {
const secret = options.secureJsonData?.azureClientSecret;
return typeof secret === 'string' && secret.length > 0 ? secret : undefined;
}
}
export function getCredentials(options: DataSourceSettings<any, any>): AzureCredentials {
const credentials = options.jsonData.azureCredentials as AzureCredentials | undefined;
// If no credentials saved, then return empty credentials
// of type based on whether the managed identity enabled
if (!credentials) {
return {
authType: config.azure.managedIdentityEnabled ? 'msi' : 'clientsecret',
azureCloud: getDefaultAzureCloud(),
};
}
switch (credentials.authType) {
case 'msi':
if (config.azure.managedIdentityEnabled) {
return {
authType: 'msi',
};
} else {
// If authentication type is managed identity but managed identities were disabled in Grafana config,
// then we should fallback to an empty app registration (client secret) configuration
return {
authType: 'clientsecret',
azureCloud: getDefaultAzureCloud(),
};
}
case 'clientsecret':
return {
authType: 'clientsecret',
azureCloud: credentials.azureCloud || getDefaultAzureCloud(),
tenantId: credentials.tenantId,
clientId: credentials.clientId,
clientSecret: getSecret(options),
};
}
}
export function updateCredentials(
options: DataSourceSettings<any, any>,
credentials: AzureCredentials
): DataSourceSettings<any, any> {
switch (credentials.authType) {
case 'msi':
if (!config.azure.managedIdentityEnabled) {
throw new Error('Managed Identity authentication is not enabled in Grafana config.');
}
options = {
...options,
jsonData: {
...options.jsonData,
azureCredentials: {
authType: 'msi',
},
},
};
return options;
case 'clientsecret':
options = {
...options,
jsonData: {
...options.jsonData,
azureCredentials: {
authType: 'clientsecret',
azureCloud: credentials.azureCloud || getDefaultAzureCloud(),
tenantId: credentials.tenantId,
clientId: credentials.clientId,
},
},
secureJsonData: {
...options.secureJsonData,
azureClientSecret:
typeof credentials.clientSecret === 'string' && credentials.clientSecret.length > 0
? credentials.clientSecret
: undefined,
},
secureJsonFields: {
...options.secureJsonFields,
azureClientSecret: typeof credentials.clientSecret === 'symbol',
},
};
return options;
}
}

View File

@@ -0,0 +1,66 @@
import React from 'react';
import { shallow } from 'enzyme';
import AzureCredentialsForm, { Props } from './AzureCredentialsForm';
const setup = (propsFunc?: (props: Props) => Props) => {
let props: Props = {
managedIdentityEnabled: false,
credentials: {
authType: 'clientsecret',
azureCloud: 'azuremonitor',
tenantId: 'e7f3f661-a933-3h3f-0294-31c4f962ec48',
clientId: '34509fad-c0r9-45df-9e25-f1ee34af6900',
clientSecret: undefined,
defaultSubscriptionId: '44987801-6nn6-49he-9b2d-9106972f9789',
},
azureCloudOptions: [
{ value: 'azuremonitor', label: 'Azure' },
{ value: 'govazuremonitor', label: 'Azure US Government' },
{ value: 'germanyazuremonitor', label: 'Azure Germany' },
{ value: 'chinaazuremonitor', label: 'Azure China' },
],
onCredentialsChange: jest.fn(),
getSubscriptions: jest.fn(),
};
if (propsFunc) {
props = propsFunc(props);
}
return shallow(<AzureCredentialsForm {...props} />);
};
describe('Render', () => {
it('should render component', () => {
const wrapper = setup();
expect(wrapper).toMatchSnapshot();
});
it('should disable azure monitor secret input', () => {
const wrapper = setup((props) => ({
...props,
credentials: {
authType: 'clientsecret',
azureCloud: 'azuremonitor',
tenantId: 'e7f3f661-a933-3h3f-0294-31c4f962ec48',
clientId: '34509fad-c0r9-45df-9e25-f1ee34af6900',
clientSecret: Symbol(),
},
}));
expect(wrapper).toMatchSnapshot();
});
it('should enable azure monitor load subscriptions button', () => {
const wrapper = setup((props) => ({
...props,
credentials: {
authType: 'clientsecret',
azureCloud: 'azuremonitor',
tenantId: 'e7f3f661-a933-3h3f-0294-31c4f962ec48',
clientId: '34509fad-c0r9-45df-9e25-f1ee34af6900',
clientSecret: 'e7f3f661-a933-4b3f-8176-51c4f982ec48',
},
}));
expect(wrapper).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,279 @@
import React, { ChangeEvent, FunctionComponent, useEffect, useReducer, useState } from 'react';
import { SelectableValue } from '@grafana/data';
import { InlineFormLabel, Button } from '@grafana/ui/src/components';
import { Select } from '@grafana/ui/src/components/Forms/Legacy/Select/Select';
import { Input } from '@grafana/ui/src/components/Forms/Legacy/Input/Input';
import { AzureAuthType, AzureCredentials, isCredentialsComplete } from './AzureCredentials';
export interface Props {
managedIdentityEnabled: boolean;
credentials: AzureCredentials;
azureCloudOptions?: SelectableValue[];
onCredentialsChange: (updatedCredentials: AzureCredentials) => void;
getSubscriptions?: () => Promise<SelectableValue[]>;
}
const authTypeOptions: Array<SelectableValue<AzureAuthType>> = [
{
value: 'msi',
label: 'Managed Identity',
},
{
value: 'clientsecret',
label: 'App Registration',
},
];
export const AzureCredentialsForm: FunctionComponent<Props> = (props: Props) => {
const { credentials, azureCloudOptions, onCredentialsChange, getSubscriptions } = props;
const hasRequiredFields = isCredentialsComplete(credentials);
const [subscriptions, setSubscriptions] = useState<Array<SelectableValue<string>>>([]);
const [loadSubscriptionsClicked, onLoadSubscriptions] = useReducer((val) => val + 1, 0);
useEffect(() => {
if (!getSubscriptions || !hasRequiredFields) {
updateSubscriptions([]);
return;
}
let canceled = false;
getSubscriptions().then((result) => {
if (!canceled) {
updateSubscriptions(result, loadSubscriptionsClicked);
}
});
return () => {
canceled = true;
};
// This effect is intended to be called only once initially and on Load Subscriptions click
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [loadSubscriptionsClicked]);
const updateSubscriptions = (received: Array<SelectableValue<string>>, autoSelect = false) => {
setSubscriptions(received);
if (getSubscriptions) {
if (autoSelect && !credentials.defaultSubscriptionId && received.length > 0) {
// Selecting the default subscription if subscriptions received but no default subscription selected
onSubscriptionChange(received[0]);
} else if (credentials.defaultSubscriptionId) {
const found = received.find((opt) => opt.value === credentials.defaultSubscriptionId);
if (!found) {
// Unselecting the default subscription if it isn't found among the received subscriptions
onSubscriptionChange(undefined);
}
}
}
};
const onAuthTypeChange = (selected: SelectableValue<AzureAuthType>) => {
if (onCredentialsChange) {
setSubscriptions([]);
const updated: AzureCredentials = {
...credentials,
authType: selected.value || 'msi',
defaultSubscriptionId: undefined,
};
onCredentialsChange(updated);
}
};
const onAzureCloudChange = (selected: SelectableValue<string>) => {
if (onCredentialsChange && credentials.authType === 'clientsecret') {
setSubscriptions([]);
const updated: AzureCredentials = {
...credentials,
azureCloud: selected.value,
defaultSubscriptionId: undefined,
};
onCredentialsChange(updated);
}
};
const onTenantIdChange = (event: ChangeEvent<HTMLInputElement>) => {
if (onCredentialsChange && credentials.authType === 'clientsecret') {
setSubscriptions([]);
const updated: AzureCredentials = {
...credentials,
tenantId: event.target.value,
defaultSubscriptionId: undefined,
};
onCredentialsChange(updated);
}
};
const onClientIdChange = (event: ChangeEvent<HTMLInputElement>) => {
if (onCredentialsChange && credentials.authType === 'clientsecret') {
setSubscriptions([]);
const updated: AzureCredentials = {
...credentials,
clientId: event.target.value,
defaultSubscriptionId: undefined,
};
onCredentialsChange(updated);
}
};
const onClientSecretChange = (event: ChangeEvent<HTMLInputElement>) => {
if (onCredentialsChange && credentials.authType === 'clientsecret') {
setSubscriptions([]);
const updated: AzureCredentials = {
...credentials,
clientSecret: event.target.value,
defaultSubscriptionId: undefined,
};
onCredentialsChange(updated);
}
};
const onClientSecretReset = () => {
if (onCredentialsChange && credentials.authType === 'clientsecret') {
setSubscriptions([]);
const updated: AzureCredentials = {
...credentials,
clientSecret: '',
defaultSubscriptionId: undefined,
};
onCredentialsChange(updated);
}
};
const onSubscriptionChange = (selected: SelectableValue<string> | undefined) => {
if (onCredentialsChange) {
const updated: AzureCredentials = {
...credentials,
defaultSubscriptionId: selected?.value,
};
onCredentialsChange(updated);
}
};
return (
<div className="gf-form-group">
{props.managedIdentityEnabled && (
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel className="width-12" tooltip="Choose the type of authentication to Azure services">
Authentication
</InlineFormLabel>
<Select
className="width-15"
value={authTypeOptions.find((opt) => opt.value === credentials.authType)}
options={authTypeOptions}
onChange={onAuthTypeChange}
/>
</div>
</div>
)}
{credentials.authType === 'clientsecret' && (
<>
{azureCloudOptions && (
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel className="width-12" tooltip="Choose an Azure Cloud">
Azure Cloud
</InlineFormLabel>
<Select
className="width-15"
value={azureCloudOptions.find((opt) => opt.value === credentials.azureCloud)}
options={azureCloudOptions}
onChange={onAzureCloudChange}
/>
</div>
</div>
)}
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel className="width-12">Directory (tenant) ID</InlineFormLabel>
<div className="width-15">
<Input
className="width-30"
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value={credentials.tenantId || ''}
onChange={onTenantIdChange}
/>
</div>
</div>
</div>
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel className="width-12">Application (client) ID</InlineFormLabel>
<div className="width-15">
<Input
className="width-30"
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value={credentials.clientId || ''}
onChange={onClientIdChange}
/>
</div>
</div>
</div>
{typeof credentials.clientSecret === 'symbol' ? (
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel className="width-12">Client Secret</InlineFormLabel>
<Input className="width-25" placeholder="configured" disabled={true} />
</div>
<div className="gf-form">
<div className="max-width-30 gf-form-inline">
<Button variant="secondary" type="button" onClick={onClientSecretReset}>
reset
</Button>
</div>
</div>
</div>
) : (
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel className="width-12">Client Secret</InlineFormLabel>
<div className="width-15">
<Input
className="width-30"
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value={credentials.clientSecret || ''}
onChange={onClientSecretChange}
/>
</div>
</div>
</div>
)}
</>
)}
{getSubscriptions && (
<>
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel className="width-12">Default Subscription</InlineFormLabel>
<div className="width-25">
<Select
value={
credentials.defaultSubscriptionId
? subscriptions.find((opt) => opt.value === credentials.defaultSubscriptionId)
: undefined
}
options={subscriptions}
onChange={onSubscriptionChange}
/>
</div>
</div>
</div>
<div className="gf-form-inline">
<div className="gf-form">
<div className="max-width-30 gf-form-inline">
<Button
variant="secondary"
size="sm"
type="button"
onClick={onLoadSubscriptions}
disabled={!hasRequiredFields}
>
Load Subscriptions
</Button>
</div>
</div>
</div>
</>
)}
</div>
);
};
export default AzureCredentialsForm;

View File

@@ -1,13 +1,20 @@
import React from 'react';
import { AlertingSettings, DataSourceHttpSettings } from '@grafana/ui';
import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
import { PromSettings } from './PromSettings';
import { PromOptions } from '../types';
import { config } from 'app/core/config';
import { PromOptions } from '../types';
import { AzureAuthSettings } from './AzureAuthSettings';
import { PromSettings } from './PromSettings';
export type Props = DataSourcePluginOptionsEditorProps<PromOptions>;
export const ConfigEditor = (props: Props) => {
const { options, onOptionsChange } = props;
const azureAuthSettings = {
azureAuthEnabled: config.featureToggles['prometheus_azure_auth'] ?? false,
azureSettingsUI: AzureAuthSettings,
};
return (
<>
<DataSourceHttpSettings
@@ -16,6 +23,7 @@ export const ConfigEditor = (props: Props) => {
showAccessOptions={true}
onChange={onOptionsChange}
sigV4AuthToggleEnabled={config.sigV4AuthEnabled}
azureAuthSettings={azureAuthSettings}
/>
<AlertingSettings<PromOptions> options={options} onOptionsChange={onOptionsChange} />

View File

@@ -0,0 +1,620 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render should disable azure monitor secret input 1`] = `
<div
className="gf-form-group"
>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
tooltip="Choose an Azure Cloud"
>
Azure Cloud
</FormLabel>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className="width-15"
components={
Object {
"Group": [Function],
"IndicatorsContainer": [Function],
"MenuList": [Function],
"Option": [Function],
"SingleValue": [Function],
}
}
isClearable={false}
isDisabled={false}
isLoading={false}
isMulti={false}
isSearchable={true}
maxMenuHeight={300}
onChange={[Function]}
openMenuOnFocus={false}
options={
Array [
Object {
"label": "Azure",
"value": "azuremonitor",
},
Object {
"label": "Azure US Government",
"value": "govazuremonitor",
},
Object {
"label": "Azure Germany",
"value": "germanyazuremonitor",
},
Object {
"label": "Azure China",
"value": "chinaazuremonitor",
},
]
}
tabSelectsValue={true}
value={
Object {
"label": "Azure",
"value": "azuremonitor",
}
}
/>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Directory (tenant) ID
</FormLabel>
<div
className="width-15"
>
<Input
className="width-30"
onChange={[Function]}
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value="e7f3f661-a933-3h3f-0294-31c4f962ec48"
/>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Application (client) ID
</FormLabel>
<div
className="width-15"
>
<Input
className="width-30"
onChange={[Function]}
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value="34509fad-c0r9-45df-9e25-f1ee34af6900"
/>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Client Secret
</FormLabel>
<Input
className="width-25"
disabled={true}
placeholder="configured"
/>
</div>
<div
className="gf-form"
>
<div
className="max-width-30 gf-form-inline"
>
<Button
onClick={[Function]}
type="button"
variant="secondary"
>
reset
</Button>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Default Subscription
</FormLabel>
<div
className="width-25"
>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className=""
components={
Object {
"Group": [Function],
"IndicatorsContainer": [Function],
"MenuList": [Function],
"Option": [Function],
"SingleValue": [Function],
}
}
isClearable={false}
isDisabled={false}
isLoading={false}
isMulti={false}
isSearchable={true}
maxMenuHeight={300}
onChange={[Function]}
openMenuOnFocus={false}
options={Array []}
tabSelectsValue={true}
/>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<div
className="max-width-30 gf-form-inline"
>
<Button
disabled={false}
onClick={[Function]}
size="sm"
type="button"
variant="secondary"
>
Load Subscriptions
</Button>
</div>
</div>
</div>
</div>
`;
exports[`Render should enable azure monitor load subscriptions button 1`] = `
<div
className="gf-form-group"
>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
tooltip="Choose an Azure Cloud"
>
Azure Cloud
</FormLabel>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className="width-15"
components={
Object {
"Group": [Function],
"IndicatorsContainer": [Function],
"MenuList": [Function],
"Option": [Function],
"SingleValue": [Function],
}
}
isClearable={false}
isDisabled={false}
isLoading={false}
isMulti={false}
isSearchable={true}
maxMenuHeight={300}
onChange={[Function]}
openMenuOnFocus={false}
options={
Array [
Object {
"label": "Azure",
"value": "azuremonitor",
},
Object {
"label": "Azure US Government",
"value": "govazuremonitor",
},
Object {
"label": "Azure Germany",
"value": "germanyazuremonitor",
},
Object {
"label": "Azure China",
"value": "chinaazuremonitor",
},
]
}
tabSelectsValue={true}
value={
Object {
"label": "Azure",
"value": "azuremonitor",
}
}
/>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Directory (tenant) ID
</FormLabel>
<div
className="width-15"
>
<Input
className="width-30"
onChange={[Function]}
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value="e7f3f661-a933-3h3f-0294-31c4f962ec48"
/>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Application (client) ID
</FormLabel>
<div
className="width-15"
>
<Input
className="width-30"
onChange={[Function]}
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value="34509fad-c0r9-45df-9e25-f1ee34af6900"
/>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Client Secret
</FormLabel>
<div
className="width-15"
>
<Input
className="width-30"
onChange={[Function]}
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value="e7f3f661-a933-4b3f-8176-51c4f982ec48"
/>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Default Subscription
</FormLabel>
<div
className="width-25"
>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className=""
components={
Object {
"Group": [Function],
"IndicatorsContainer": [Function],
"MenuList": [Function],
"Option": [Function],
"SingleValue": [Function],
}
}
isClearable={false}
isDisabled={false}
isLoading={false}
isMulti={false}
isSearchable={true}
maxMenuHeight={300}
onChange={[Function]}
openMenuOnFocus={false}
options={Array []}
tabSelectsValue={true}
/>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<div
className="max-width-30 gf-form-inline"
>
<Button
disabled={false}
onClick={[Function]}
size="sm"
type="button"
variant="secondary"
>
Load Subscriptions
</Button>
</div>
</div>
</div>
</div>
`;
exports[`Render should render component 1`] = `
<div
className="gf-form-group"
>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
tooltip="Choose an Azure Cloud"
>
Azure Cloud
</FormLabel>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className="width-15"
components={
Object {
"Group": [Function],
"IndicatorsContainer": [Function],
"MenuList": [Function],
"Option": [Function],
"SingleValue": [Function],
}
}
isClearable={false}
isDisabled={false}
isLoading={false}
isMulti={false}
isSearchable={true}
maxMenuHeight={300}
onChange={[Function]}
openMenuOnFocus={false}
options={
Array [
Object {
"label": "Azure",
"value": "azuremonitor",
},
Object {
"label": "Azure US Government",
"value": "govazuremonitor",
},
Object {
"label": "Azure Germany",
"value": "germanyazuremonitor",
},
Object {
"label": "Azure China",
"value": "chinaazuremonitor",
},
]
}
tabSelectsValue={true}
value={
Object {
"label": "Azure",
"value": "azuremonitor",
}
}
/>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Directory (tenant) ID
</FormLabel>
<div
className="width-15"
>
<Input
className="width-30"
onChange={[Function]}
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value="e7f3f661-a933-3h3f-0294-31c4f962ec48"
/>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Application (client) ID
</FormLabel>
<div
className="width-15"
>
<Input
className="width-30"
onChange={[Function]}
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value="34509fad-c0r9-45df-9e25-f1ee34af6900"
/>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Client Secret
</FormLabel>
<div
className="width-15"
>
<Input
className="width-30"
onChange={[Function]}
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value=""
/>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Default Subscription
</FormLabel>
<div
className="width-25"
>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className=""
components={
Object {
"Group": [Function],
"IndicatorsContainer": [Function],
"MenuList": [Function],
"Option": [Function],
"SingleValue": [Function],
}
}
isClearable={false}
isDisabled={false}
isLoading={false}
isMulti={false}
isSearchable={true}
maxMenuHeight={300}
onChange={[Function]}
openMenuOnFocus={false}
options={Array []}
tabSelectsValue={true}
/>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<div
className="max-width-30 gf-form-inline"
>
<Button
disabled={true}
onClick={[Function]}
size="sm"
type="button"
variant="secondary"
>
Load Subscriptions
</Button>
</div>
</div>
</div>
</div>
`;

File diff suppressed because it is too large Load Diff

565
yarn.lock
View File

@@ -3898,17 +3898,39 @@
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5"
integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==
"@storybook/addon-actions@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-6.3.0.tgz#e5a24c69d70da9aa98560f19d10c06a50495ca2e"
integrity sha512-7Ls1OIAdtAa4a27/bTuAlejQee4j7bFBkRzAeaHzcaZT1VVXoF6yBfMGuEGJI8brQ+KuSaIhIU2b0Iuzq47dDQ==
"@storybook/addon-a11y@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/addon-a11y/-/addon-a11y-6.3.5.tgz#f1819b2c756bef3d69c9978e7ccac908490890fe"
integrity sha512-iczDyensBVAUkXEuHcMDe14N797EHc85IQkG894jhCTOorKde4RhTbdExk0wVcCM/psk346CECk8HHMgO9CBGQ==
dependencies:
"@storybook/addons" "6.3.0"
"@storybook/api" "6.3.0"
"@storybook/client-api" "6.3.0"
"@storybook/components" "6.3.0"
"@storybook/core-events" "6.3.0"
"@storybook/theming" "6.3.0"
"@storybook/addons" "6.3.5"
"@storybook/api" "6.3.5"
"@storybook/channels" "6.3.5"
"@storybook/client-api" "6.3.5"
"@storybook/client-logger" "6.3.5"
"@storybook/components" "6.3.5"
"@storybook/core-events" "6.3.5"
"@storybook/theming" "6.3.5"
axe-core "^4.2.0"
core-js "^3.8.2"
global "^4.4.0"
lodash "^4.17.20"
react-sizeme "^3.0.1"
regenerator-runtime "^0.13.7"
ts-dedent "^2.0.0"
util-deprecate "^1.0.2"
"@storybook/addon-actions@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-6.3.5.tgz#3fe0e4f76e1d88c16f0c68977b081c624137c15f"
integrity sha512-FdGT7Y6XSXqbmxY/SRDWKqEdYJyxshmA5xiNmJonSWiZ3e7cL3Awfqju4/7Y+kgR/gTaBylb5j8vUESWEwFwEQ==
dependencies:
"@storybook/addons" "6.3.5"
"@storybook/api" "6.3.5"
"@storybook/client-api" "6.3.5"
"@storybook/components" "6.3.5"
"@storybook/core-events" "6.3.5"
"@storybook/theming" "6.3.5"
core-js "^3.8.2"
fast-deep-equal "^3.1.3"
global "^4.4.0"
@@ -3921,17 +3943,17 @@
util-deprecate "^1.0.2"
uuid-browser "^3.1.0"
"@storybook/addon-backgrounds@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/addon-backgrounds/-/addon-backgrounds-6.3.0.tgz#0562ec41ffff479803bd4b8a9d17abea2d6d6cdd"
integrity sha512-MzqD94IDfJ9oipFKMLoJhf3zTxqQ0DVfsWXGV1o2nslg8gZFFH04yXex2kVuTiHYCuaLxfk/wnlpSyzqX2+CZQ==
"@storybook/addon-backgrounds@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/addon-backgrounds/-/addon-backgrounds-6.3.5.tgz#8f09cddb6a1e05b2e2d0456be3881c715da6efa7"
integrity sha512-Wx62sihR5022DzD98jc8EsoMLOwBe33LpCluMVfIuNSgrpn2rzRgdYTuJqsQFOTKAb+8zQvAS0qeHdBfocmLHQ==
dependencies:
"@storybook/addons" "6.3.0"
"@storybook/api" "6.3.0"
"@storybook/client-logger" "6.3.0"
"@storybook/components" "6.3.0"
"@storybook/core-events" "6.3.0"
"@storybook/theming" "6.3.0"
"@storybook/addons" "6.3.5"
"@storybook/api" "6.3.5"
"@storybook/client-logger" "6.3.5"
"@storybook/components" "6.3.5"
"@storybook/core-events" "6.3.5"
"@storybook/theming" "6.3.5"
core-js "^3.8.2"
global "^4.4.0"
memoizerific "^1.11.3"
@@ -3939,24 +3961,24 @@
ts-dedent "^2.0.0"
util-deprecate "^1.0.2"
"@storybook/addon-controls@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/addon-controls/-/addon-controls-6.3.0.tgz#30275b9508a4d1acd1f3fa8f7dd432be629c3fec"
integrity sha512-caiWFJ/iCdZPHI5rwk26fAQsf8QI7WXIoB850SYVDhkIirzJVZjugvwgrqgTfVf2Z5dWOe9aceroC9rBClHAlQ==
"@storybook/addon-controls@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/addon-controls/-/addon-controls-6.3.5.tgz#63ae24c996cf73c4ad45c836bf1b3c05dcce10ea"
integrity sha512-5wrCm5JPFvXemCptf7UAuHMJe0A31aWaSDQpLB8aglLQhi9Xwlff9Js3MgDwmMyEjkjblqNmQfe0sckH35bHag==
dependencies:
"@storybook/addons" "6.3.0"
"@storybook/api" "6.3.0"
"@storybook/client-api" "6.3.0"
"@storybook/components" "6.3.0"
"@storybook/node-logger" "6.3.0"
"@storybook/theming" "6.3.0"
"@storybook/addons" "6.3.5"
"@storybook/api" "6.3.5"
"@storybook/client-api" "6.3.5"
"@storybook/components" "6.3.5"
"@storybook/node-logger" "6.3.5"
"@storybook/theming" "6.3.5"
core-js "^3.8.2"
ts-dedent "^2.0.0"
"@storybook/addon-docs@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-6.3.0.tgz#b8b7f3b8a38d78b7c63ba2aa9b87bf078e8e942b"
integrity sha512-FpANy+7J3jpoxUoMfqwAetMatwbxQctOwN+Eh95uwQWYRZwsNHqdTv72/rtHiWR9wMaYThok5vqYHFvCpQTVPw==
"@storybook/addon-docs@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-6.3.5.tgz#e0febd3dcfbbc21c54dfda3c43fe9ef5ee13f2b7"
integrity sha512-jR9HcWDHxMMaOpPPmaNpcr4TEglrAy8AHRNqD+P8DhPc3VQOHcxnSfn30k3vGP02WELvEDreTOjsQBhVMweT7A==
dependencies:
"@babel/core" "^7.12.10"
"@babel/generator" "^7.12.11"
@@ -3967,20 +3989,20 @@
"@mdx-js/loader" "^1.6.22"
"@mdx-js/mdx" "^1.6.22"
"@mdx-js/react" "^1.6.22"
"@storybook/addons" "6.3.0"
"@storybook/api" "6.3.0"
"@storybook/builder-webpack4" "6.3.0"
"@storybook/client-api" "6.3.0"
"@storybook/client-logger" "6.3.0"
"@storybook/components" "6.3.0"
"@storybook/core" "6.3.0"
"@storybook/core-events" "6.3.0"
"@storybook/addons" "6.3.5"
"@storybook/api" "6.3.5"
"@storybook/builder-webpack4" "6.3.5"
"@storybook/client-api" "6.3.5"
"@storybook/client-logger" "6.3.5"
"@storybook/components" "6.3.5"
"@storybook/core" "6.3.5"
"@storybook/core-events" "6.3.5"
"@storybook/csf" "0.0.1"
"@storybook/csf-tools" "6.3.0"
"@storybook/node-logger" "6.3.0"
"@storybook/postinstall" "6.3.0"
"@storybook/source-loader" "6.3.0"
"@storybook/theming" "6.3.0"
"@storybook/csf-tools" "6.3.5"
"@storybook/node-logger" "6.3.5"
"@storybook/postinstall" "6.3.5"
"@storybook/source-loader" "6.3.5"
"@storybook/theming" "6.3.5"
acorn "^7.4.1"
acorn-jsx "^5.3.1"
acorn-walk "^7.2.0"
@@ -4003,27 +4025,27 @@
ts-dedent "^2.0.0"
util-deprecate "^1.0.2"
"@storybook/addon-essentials@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/addon-essentials/-/addon-essentials-6.3.0.tgz#8b0329042e0f25192c04c78eac5c38d4d8259a62"
integrity sha512-8ejOP3l4UC2utDbcq8QUQ2nOqAOzL9ri20So5qrlTuBPtMmSNUea7p5yAGB0GOJ9j96k3pS2nU1/dlEqepo5nA==
"@storybook/addon-essentials@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/addon-essentials/-/addon-essentials-6.3.5.tgz#b2856f604a3db5eb76217400105c0726aedcbef9"
integrity sha512-jKxvpBB9bb3mBehAXABWtbLAB9b7DHT4WY6yMvnUFlmUaVn4vOAU+Jwxu7kwzU5/240scKgaZm1OIlBWIQF9Xw==
dependencies:
"@storybook/addon-actions" "6.3.0"
"@storybook/addon-backgrounds" "6.3.0"
"@storybook/addon-controls" "6.3.0"
"@storybook/addon-docs" "6.3.0"
"@storybook/addon-measure" "^1.2.3"
"@storybook/addon-toolbars" "6.3.0"
"@storybook/addon-viewport" "6.3.0"
"@storybook/addons" "6.3.0"
"@storybook/api" "6.3.0"
"@storybook/node-logger" "6.3.0"
"@storybook/addon-actions" "6.3.5"
"@storybook/addon-backgrounds" "6.3.5"
"@storybook/addon-controls" "6.3.5"
"@storybook/addon-docs" "6.3.5"
"@storybook/addon-measure" "^2.0.0"
"@storybook/addon-toolbars" "6.3.5"
"@storybook/addon-viewport" "6.3.5"
"@storybook/addons" "6.3.5"
"@storybook/api" "6.3.5"
"@storybook/node-logger" "6.3.5"
core-js "^3.8.2"
regenerator-runtime "^0.13.7"
storybook-addon-outline "^1.3.3"
storybook-addon-outline "^1.4.1"
ts-dedent "^2.0.0"
"@storybook/addon-knobs@6.3.0":
"@storybook/addon-knobs@6.3.5":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/addon-knobs/-/addon-knobs-6.3.0.tgz#f289c072729651150a27a163371df20922c24f93"
integrity sha512-wsZZ1t38KHdaxzrc9oPyiIJDihJnjHRRabrENQbylktJwETEjb2z3eX0iBRJGiz/YCHO+tGd0ItyZArOdijT6g==
@@ -4039,23 +4061,23 @@
react-lifecycles-compat "^3.0.4"
react-select "^3.2.0"
"@storybook/addon-measure@^1.2.3":
version "1.2.4"
resolved "https://registry.yarnpkg.com/@storybook/addon-measure/-/addon-measure-1.2.4.tgz#149705ef9de5e9251c012deb84406b3bc9307452"
integrity sha512-pxAo7QcETdiienZYMjAhX/3BqPnYTuH0ZSjmJzsr+yCBvZmZUciq1h3WvyUN59rT0ewFwLTKsmZG/wVZj+aN+Q==
"@storybook/addon-measure@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@storybook/addon-measure/-/addon-measure-2.0.0.tgz#c40bbe91bacd3f795963dc1ee6ff86be87deeda9"
integrity sha512-ZhdT++cX+L9LwjhGYggvYUUVQH/MGn2rwbrAwCMzA/f2QTFvkjxzX8nDgMxIhaLCDC+gHIxfJG2wrWN0jkBr3g==
"@storybook/addon-storysource@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/addon-storysource/-/addon-storysource-6.3.0.tgz#b523abd4592ce494366109eedb2c04501029bbf2"
integrity sha512-Vk0oswJ0hWYqjypHox+HRj/iovyc42w6B88ZL1yjdH45+bQYjMnxE2yqY4kGVSZKQMkZidjWGKtgD8Y8xP4TXA==
"@storybook/addon-storysource@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/addon-storysource/-/addon-storysource-6.3.5.tgz#fe988e2bec5004e1aa58aa926081cca8a8b62dc9"
integrity sha512-N7lmiPdMU12lKJqnckAQKXHKroxSyD9UKI5SZLdCaDxmOUwH6D82x4CWB/yUQYFrhhlhxYFFVSmbq/XkokmuIw==
dependencies:
"@storybook/addons" "6.3.0"
"@storybook/api" "6.3.0"
"@storybook/client-logger" "6.3.0"
"@storybook/components" "6.3.0"
"@storybook/router" "6.3.0"
"@storybook/source-loader" "6.3.0"
"@storybook/theming" "6.3.0"
"@storybook/addons" "6.3.5"
"@storybook/api" "6.3.5"
"@storybook/client-logger" "6.3.5"
"@storybook/components" "6.3.5"
"@storybook/router" "6.3.5"
"@storybook/source-loader" "6.3.5"
"@storybook/theming" "6.3.5"
core-js "^3.8.2"
estraverse "^5.2.0"
loader-utils "^2.0.0"
@@ -4064,64 +4086,64 @@
react-syntax-highlighter "^13.5.3"
regenerator-runtime "^0.13.7"
"@storybook/addon-toolbars@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/addon-toolbars/-/addon-toolbars-6.3.0.tgz#5e5837812c7ba94e4d5be3b02b0f915a33b4f98b"
integrity sha512-E0LwAaoWNtmPfMq9GbySsK2ZdXlPf9gJQD1uI3KXbcaGBhtY136QmZS+VpUmPfilplrYJ2G6GAQoPHrIPUf1VQ==
"@storybook/addon-toolbars@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/addon-toolbars/-/addon-toolbars-6.3.5.tgz#a88b476795d1ecf32ad08e69d5188ec10868d24e"
integrity sha512-167CMhcevkepJStbvjMfFzU+/MdMU5hhr69N99m2M2ZeJnp5q6rXHMVvcjgBmKJsuAkLd4BPJ1U5GAPOS9oNHg==
dependencies:
"@storybook/addons" "6.3.0"
"@storybook/api" "6.3.0"
"@storybook/client-api" "6.3.0"
"@storybook/components" "6.3.0"
"@storybook/theming" "6.3.0"
"@storybook/addons" "6.3.5"
"@storybook/api" "6.3.5"
"@storybook/client-api" "6.3.5"
"@storybook/components" "6.3.5"
"@storybook/theming" "6.3.5"
core-js "^3.8.2"
regenerator-runtime "^0.13.7"
"@storybook/addon-viewport@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/addon-viewport/-/addon-viewport-6.3.0.tgz#a30660fe1873f16e955798718e3f14e26f4bff09"
integrity sha512-aOENuKIfmeQOhm++p2ezwIV9gET05s5/QQ1TTZrrPixQ3FxmCwAb/OqsmD4m/8e075C5gLXQEV47vGAkYyTm0Q==
"@storybook/addon-viewport@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/addon-viewport/-/addon-viewport-6.3.5.tgz#df3a12ff36b46eb302d386f37d35bd05c290b733"
integrity sha512-XklcnqbbtZgVMyoAuF8LO45t/M/zjqq51qmQviizVFlbcL3MKAGGnSPQjqYvDv34kkkkfFXIshfv/+diWqkpNQ==
dependencies:
"@storybook/addons" "6.3.0"
"@storybook/api" "6.3.0"
"@storybook/client-logger" "6.3.0"
"@storybook/components" "6.3.0"
"@storybook/core-events" "6.3.0"
"@storybook/theming" "6.3.0"
"@storybook/addons" "6.3.5"
"@storybook/api" "6.3.5"
"@storybook/client-logger" "6.3.5"
"@storybook/components" "6.3.5"
"@storybook/core-events" "6.3.5"
"@storybook/theming" "6.3.5"
core-js "^3.8.2"
global "^4.4.0"
memoizerific "^1.11.3"
prop-types "^15.7.2"
regenerator-runtime "^0.13.7"
"@storybook/addons@6.3.0", "@storybook/addons@^6.3.0-beta.6":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-6.3.0.tgz#a86849f46a654d2d78b91fad0088264a32d4e58e"
integrity sha512-/dcq20HtdSw5+cG8znR59Y/uv2zCR2VjRK3N52IkGWk162b/UbSjjL0PhNnnQFGpH9Fruft6mqvjTAKT41kmJw==
"@storybook/addons@6.3.5", "@storybook/addons@^6.3.0":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-6.3.5.tgz#af7fec11cd780d64d0927840748d18e4df43e5d7"
integrity sha512-7IKTTzISRZdmdZrV/+CmcOfH3O1y/vWn+apkooWLwFhFkEKJXdmAzJ3A1uRmze4mF0ahdMTGVPm44DGlbGqv4g==
dependencies:
"@storybook/api" "6.3.0"
"@storybook/channels" "6.3.0"
"@storybook/client-logger" "6.3.0"
"@storybook/core-events" "6.3.0"
"@storybook/router" "6.3.0"
"@storybook/theming" "6.3.0"
"@storybook/api" "6.3.5"
"@storybook/channels" "6.3.5"
"@storybook/client-logger" "6.3.5"
"@storybook/core-events" "6.3.5"
"@storybook/router" "6.3.5"
"@storybook/theming" "6.3.5"
core-js "^3.8.2"
global "^4.4.0"
regenerator-runtime "^0.13.7"
"@storybook/api@6.3.0", "@storybook/api@^6.3.0-beta.6":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.3.0.tgz#5ecb646e7c3c4c7c494bb15f4c94554f7f4ee09e"
integrity sha512-swPMcQadLDRTnMjL9dwY6K1zXHn3KcAa3euvSHd1R4OKXTSBBj1zHvIaOrq6yHz7RIYOACmZlEV3CUru9FlvEA==
"@storybook/api@6.3.5", "@storybook/api@^6.3.0":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.3.5.tgz#3edaf32029fba018917ba25a307f471ddd165cc0"
integrity sha512-H5DfvreRbiN7O+VBCbHraM+xNvdzs60ehC7P2jGRAkbzurPjQAmVCMKkPVKzOF9JA5cGOyv/ctkDn/9PFfEl9Q==
dependencies:
"@reach/router" "^1.3.4"
"@storybook/channels" "6.3.0"
"@storybook/client-logger" "6.3.0"
"@storybook/core-events" "6.3.0"
"@storybook/channels" "6.3.5"
"@storybook/client-logger" "6.3.5"
"@storybook/core-events" "6.3.5"
"@storybook/csf" "0.0.1"
"@storybook/router" "6.3.0"
"@storybook/router" "6.3.5"
"@storybook/semver" "^7.3.2"
"@storybook/theming" "6.3.0"
"@storybook/theming" "6.3.5"
"@types/reach__router" "^1.3.7"
core-js "^3.8.2"
fast-deep-equal "^3.1.3"
@@ -4135,10 +4157,10 @@
ts-dedent "^2.0.0"
util-deprecate "^1.0.2"
"@storybook/builder-webpack4@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/builder-webpack4/-/builder-webpack4-6.3.0.tgz#ce6d832c4e6f7ed25d4362aed5be4ab1db67b85a"
integrity sha512-s2s9uVNIvj/CFQOwA9B8nbOKHNtVj5wIIeeR8cNAGWKxoDNA1YFAqSrmLGWDtxpZpADmJXzmVKMQts7MjkKdKg==
"@storybook/builder-webpack4@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/builder-webpack4/-/builder-webpack4-6.3.5.tgz#71fbbd849a6d1e987ffa6291057bc4a74b1c58e6"
integrity sha512-jQZGHYiwZbB8ByfRQSJk6uYraUGFmQ/qAX9K5B0Yd2OE6AVdN6hxwSjjXjjKwm3HQTzvJfG75sByU76+xUjRLw==
dependencies:
"@babel/core" "^7.12.10"
"@babel/plugin-proposal-class-properties" "^7.12.1"
@@ -4161,20 +4183,20 @@
"@babel/preset-env" "^7.12.11"
"@babel/preset-react" "^7.12.10"
"@babel/preset-typescript" "^7.12.7"
"@storybook/addons" "6.3.0"
"@storybook/api" "6.3.0"
"@storybook/channel-postmessage" "6.3.0"
"@storybook/channels" "6.3.0"
"@storybook/client-api" "6.3.0"
"@storybook/client-logger" "6.3.0"
"@storybook/components" "6.3.0"
"@storybook/core-common" "6.3.0"
"@storybook/core-events" "6.3.0"
"@storybook/node-logger" "6.3.0"
"@storybook/router" "6.3.0"
"@storybook/addons" "6.3.5"
"@storybook/api" "6.3.5"
"@storybook/channel-postmessage" "6.3.5"
"@storybook/channels" "6.3.5"
"@storybook/client-api" "6.3.5"
"@storybook/client-logger" "6.3.5"
"@storybook/components" "6.3.5"
"@storybook/core-common" "6.3.5"
"@storybook/core-events" "6.3.5"
"@storybook/node-logger" "6.3.5"
"@storybook/router" "6.3.5"
"@storybook/semver" "^7.3.2"
"@storybook/theming" "6.3.0"
"@storybook/ui" "6.3.0"
"@storybook/theming" "6.3.5"
"@storybook/ui" "6.3.5"
"@types/node" "^14.0.10"
"@types/webpack" "^4.41.26"
autoprefixer "^9.8.6"
@@ -4211,38 +4233,38 @@
webpack-hot-middleware "^2.25.0"
webpack-virtual-modules "^0.2.2"
"@storybook/channel-postmessage@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-6.3.0.tgz#96e7aea034ec1c4f397323ab7923eaa80d017324"
integrity sha512-q7FeNWIIrvZxycIMBscqahFLygxAa2L4eJ9oxZFF9zJpSV80bxDalMou3Uo7RvDJFrAeHCanF1Y7bnEDMus4yg==
"@storybook/channel-postmessage@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-6.3.5.tgz#3f3c1b2dc58babbdfb3eb2a7bece4fbcc7d8383e"
integrity sha512-KQfPfoFV4VfzcFKT3xINywWkGdJPWdwiOUycz5/N9uNgl6KXurrGp+GHolUPiYnf4Z7AGVC+CAR7/WjxIIm85g==
dependencies:
"@storybook/channels" "6.3.0"
"@storybook/client-logger" "6.3.0"
"@storybook/core-events" "6.3.0"
"@storybook/channels" "6.3.5"
"@storybook/client-logger" "6.3.5"
"@storybook/core-events" "6.3.5"
core-js "^3.8.2"
global "^4.4.0"
qs "^6.10.0"
telejson "^5.3.2"
"@storybook/channels@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.3.0.tgz#f378c6ee03e0c72a2ee9263c8dfdfa4a7a1bcf51"
integrity sha512-E+SCQLSIlCaOGKEkZ8rFKNyH24/N4IA6h+EDF+9mhw3yT4iv7NCoswCgqX7JhyhSjWkM01onhuMVUVKVvi7CSw==
"@storybook/channels@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.3.5.tgz#8c660057b2cc48859dbda269e9bc4f71be260a46"
integrity sha512-kYR822+NLSokY0JkOBSDJCC8woXx9T4m6IaHS3/R4BhurSS80Jt09YWc8nQPKTAkkTrSSnzKiS1D/yVGsGbMjg==
dependencies:
core-js "^3.8.2"
ts-dedent "^2.0.0"
util-deprecate "^1.0.2"
"@storybook/client-api@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-6.3.0.tgz#a285c4b64ec318f360ade31d0c87c22e6e1db2a6"
integrity sha512-5HLtYPBOHif9AdzwLCrVbMQdOJ2dne9zv7oTo6Yl0wvLhbr6V/VypoXE0CgFF3hAI2iUquG5z00KlrE8UErC5Q==
"@storybook/client-api@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-6.3.5.tgz#243ab8a9616298db1451ee7a134d18136aefeb50"
integrity sha512-GU5jOS3i9oLTl74Xd5GUV+l06dx64BkDlPgaeGpKcoQlIwReFNVZxobZazaroYij8mCNUnQpgM/ArRAG8Uz8Cw==
dependencies:
"@storybook/addons" "6.3.0"
"@storybook/channel-postmessage" "6.3.0"
"@storybook/channels" "6.3.0"
"@storybook/client-logger" "6.3.0"
"@storybook/core-events" "6.3.0"
"@storybook/addons" "6.3.5"
"@storybook/channel-postmessage" "6.3.5"
"@storybook/channels" "6.3.5"
"@storybook/client-logger" "6.3.5"
"@storybook/core-events" "6.3.5"
"@storybook/csf" "0.0.1"
"@types/qs" "^6.9.5"
"@types/webpack-env" "^1.16.0"
@@ -4257,23 +4279,23 @@
ts-dedent "^2.0.0"
util-deprecate "^1.0.2"
"@storybook/client-logger@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.3.0.tgz#3188f84dd10353d225efadee9f24928395d38aab"
integrity sha512-x/y820f/2Jm6RW5TxRv7IlbF6zWpTkHoajfwYuTpK/OXvK5gx6dwXGdgjNoaAGofGRz5SVjDjTDPOcd5X5AUPw==
"@storybook/client-logger@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.3.5.tgz#428b69c6433433372cc8265f285da9707fed63ab"
integrity sha512-vPD8MYf/847lsYGhq9eFmcAi+fQc2p6fue9McEreHviaT3S3Sk5/u8L2f8D8sdpym0yPJrHBmla+fK45MnxQLg==
dependencies:
core-js "^3.8.2"
global "^4.4.0"
"@storybook/components@6.3.0", "@storybook/components@^6.3.0-beta.6":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-6.3.0.tgz#5ad372abd60ee0cb02516f960f514659e3fbf865"
integrity sha512-TDcazQAtNgE1E33jKKABx51XpvWyXMcJZFWA0d5wu8XrElrL1PuZqz7dPePoWKGMfTaPYWP6rRyDg4Svv36j+A==
"@storybook/components@6.3.5", "@storybook/components@^6.3.0":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-6.3.5.tgz#7e2f2d15c3552de6507727bfa4a13a20facd81e5"
integrity sha512-EYyUhxuuted3fmPC8055cxG4kMf8tp/sjI6l2wAQ5myRZ3zBpV0mjOxxgOeiDphtBnyDKmOF+4x8EkSUJmu/5w==
dependencies:
"@popperjs/core" "^2.6.0"
"@storybook/client-logger" "6.3.0"
"@storybook/client-logger" "6.3.5"
"@storybook/csf" "0.0.1"
"@storybook/theming" "6.3.0"
"@storybook/theming" "6.3.5"
"@types/color-convert" "^2.0.0"
"@types/overlayscrollbars" "^1.12.0"
"@types/react-syntax-highlighter" "11.0.5"
@@ -4295,18 +4317,18 @@
ts-dedent "^2.0.0"
util-deprecate "^1.0.2"
"@storybook/core-client@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/core-client/-/core-client-6.3.0.tgz#d7acf4a6071cbac76a7d38e232640fc37b6a2e72"
integrity sha512-S2MZmHGjkZdGYgkWNXzn3Z/AS2NeiYVyO503mF7d+4zfgAoasKBkc7Y/1Ry3RuaGRwOq5bNQtJUZsF0kX1a8iQ==
"@storybook/core-client@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/core-client/-/core-client-6.3.5.tgz#8187547c052521859a6f8f0a4047ec53655582a8"
integrity sha512-hOvTzeiCDnOK7eD40wfI10JZ7BI7IRyzCyIKxRaROq8x53TpBHbuOKGhJFddUVJLT+w/BVA5y0Hf9AOMgn68Iw==
dependencies:
"@storybook/addons" "6.3.0"
"@storybook/channel-postmessage" "6.3.0"
"@storybook/client-api" "6.3.0"
"@storybook/client-logger" "6.3.0"
"@storybook/core-events" "6.3.0"
"@storybook/addons" "6.3.5"
"@storybook/channel-postmessage" "6.3.5"
"@storybook/client-api" "6.3.5"
"@storybook/client-logger" "6.3.5"
"@storybook/core-events" "6.3.5"
"@storybook/csf" "0.0.1"
"@storybook/ui" "6.3.0"
"@storybook/ui" "6.3.5"
airbnb-js-shims "^2.2.1"
ansi-to-html "^0.6.11"
core-js "^3.8.2"
@@ -4318,10 +4340,10 @@
unfetch "^4.2.0"
util-deprecate "^1.0.2"
"@storybook/core-common@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-6.3.0.tgz#1310a0480bfd84d3399c3ba13e28b873b037f108"
integrity sha512-AYoN1g8g4FI2K2UcGfLAm7EUPgesiClLT/zqy2q6dWQrIUayWzJqI+gqDyYukv5s+KHRanGBZNCTBww/VhcPlg==
"@storybook/core-common@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-6.3.5.tgz#fff39c65e8872b475afd8a102e6e56ec8e914557"
integrity sha512-edLGDDbUPKZunxIL4g9TfHCVUB9owmcPxg7y3XWuM3murHS98t6uGfcSFOg5fTYIdYq7ZuwPQM08KGqAAoiRsg==
dependencies:
"@babel/core" "^7.12.10"
"@babel/plugin-proposal-class-properties" "^7.12.1"
@@ -4344,7 +4366,7 @@
"@babel/preset-react" "^7.12.10"
"@babel/preset-typescript" "^7.12.7"
"@babel/register" "^7.12.1"
"@storybook/node-logger" "6.3.0"
"@storybook/node-logger" "6.3.5"
"@storybook/semver" "^7.3.2"
"@types/glob-base" "^0.3.0"
"@types/micromatch" "^4.0.1"
@@ -4372,24 +4394,24 @@
util-deprecate "^1.0.2"
webpack "4"
"@storybook/core-events@6.3.0", "@storybook/core-events@^6.3.0-beta.6":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.3.0.tgz#5e220a866db5b93550b5c3464774a7b10ad036a6"
integrity sha512-ZGTm5nQvFLlc2LVgoDyxo99MbQcFqQzkxIQReFkO7hPwwkcjcwmdBtnlmkn9/p5QQ5/8aU0k+ceCkrBNu1M83w==
"@storybook/core-events@6.3.5", "@storybook/core-events@^6.3.0":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.3.5.tgz#40d51b39e086192dc0af11e22da011e5483ee400"
integrity sha512-1RUaOwD1STqf1EG727fLuy18dS67Nkdf/8pIGGATxAohfwHkHOhOI9tOP+GPx+aE7UioDmVWvwbUxRQ1b4YitA==
dependencies:
core-js "^3.8.2"
"@storybook/core-server@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-6.3.0.tgz#06025c0f920d827649465ddb76b3766cf4bd313e"
integrity sha512-6Lckos2bleYb0Qg0JXhLSyqASKMquueefIjde5ySelyJzZLyW8ZYt+sfKu7+rdi/RqOvUCyfLcPHAxJSub2bRg==
"@storybook/core-server@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-6.3.5.tgz#3323f54f865258f70955ebd7ff9d23282a02cea5"
integrity sha512-bxWKXq9us1PYrFtCq5mMCQcVgPs4lqRXEWIYzfbnXTBGX+5lD0JH1lsTnm99mVqz/x/WjSrzSLO9+A6UX9/cYA==
dependencies:
"@storybook/builder-webpack4" "6.3.0"
"@storybook/core-client" "6.3.0"
"@storybook/core-common" "6.3.0"
"@storybook/csf-tools" "6.3.0"
"@storybook/manager-webpack4" "6.3.0"
"@storybook/node-logger" "6.3.0"
"@storybook/builder-webpack4" "6.3.5"
"@storybook/core-client" "6.3.5"
"@storybook/core-common" "6.3.5"
"@storybook/csf-tools" "6.3.5"
"@storybook/manager-webpack4" "6.3.5"
"@storybook/node-logger" "6.3.5"
"@storybook/semver" "^7.3.2"
"@types/node" "^14.0.10"
"@types/node-fetch" "^2.5.7"
@@ -4418,18 +4440,18 @@
util-deprecate "^1.0.2"
webpack "4"
"@storybook/core@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-6.3.0.tgz#9e2dd83629be411898fa578c9f3a883ff45a81b4"
integrity sha512-8sEhlzD0f3ewnnXutt+aBTaVJ1EuW6yR8pSSLVSSwdBRQE2UVy1YOA+0kAspq+lNrF1IrvX6WvPqJq/ZmPWcOw==
"@storybook/core@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-6.3.5.tgz#45579b741a5836023572e9721d3390a5226bc348"
integrity sha512-ZzzqxtsxjkO1RmJVSUgI7o87cQtFmhlca6sSWCInlwZYeOOgRQVukZ3KkFaPFzVbDeJtLQAST4ONNaCtUMVgwg==
dependencies:
"@storybook/core-client" "6.3.0"
"@storybook/core-server" "6.3.0"
"@storybook/core-client" "6.3.5"
"@storybook/core-server" "6.3.5"
"@storybook/csf-tools@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/csf-tools/-/csf-tools-6.3.0.tgz#6ec59ebcf8739209b7871956e3be426018a18583"
integrity sha512-7bG83511Hj6Hb1J+NrHtmzew/ib5dlgl2HjIQYWvL1xyUqBIDJNgaSixO624Yu36Yrcyv3+018hPdnn8E1nNuQ==
"@storybook/csf-tools@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/csf-tools/-/csf-tools-6.3.5.tgz#a396257323f32ea5968bfcfbccc0a6214f107694"
integrity sha512-P6bD//gG9lEJi58gE8e8zpbWFDz4L4yPFzjL7VPQBp11ZQxCj4HgFcwRnyq2jctyFjQlJNM+CjHhiuts1mqD/A==
dependencies:
"@babel/generator" "^7.12.11"
"@babel/parser" "^7.12.11"
@@ -4453,20 +4475,20 @@
dependencies:
lodash "^4.17.15"
"@storybook/manager-webpack4@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/manager-webpack4/-/manager-webpack4-6.3.0.tgz#6dca60ba152c53f766daae79cdb12e969460cd48"
integrity sha512-M4HjxKQeNaMTg7PlxVp06jmdpVHu1H4cdgXbHZcD977nJ6R7bpZ4YTlTez3TjshJLoze75FRyubOlNu0l5CdKQ==
"@storybook/manager-webpack4@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/manager-webpack4/-/manager-webpack4-6.3.5.tgz#fb33cb6cf2ac23f6755a98e4943de56aedf0ee33"
integrity sha512-MNeRv8GkJkIgr4wbUUdBkMYNSORpywEkh0Ya1aYKI2FalXu8FjUCTTPzpvkaHk01hGFlvwf6xy59WJgzvljP4g==
dependencies:
"@babel/core" "^7.12.10"
"@babel/plugin-transform-template-literals" "^7.12.1"
"@babel/preset-react" "^7.12.10"
"@storybook/addons" "6.3.0"
"@storybook/core-client" "6.3.0"
"@storybook/core-common" "6.3.0"
"@storybook/node-logger" "6.3.0"
"@storybook/theming" "6.3.0"
"@storybook/ui" "6.3.0"
"@storybook/addons" "6.3.5"
"@storybook/core-client" "6.3.5"
"@storybook/core-common" "6.3.5"
"@storybook/node-logger" "6.3.5"
"@storybook/theming" "6.3.5"
"@storybook/ui" "6.3.5"
"@types/node" "^14.0.10"
"@types/webpack" "^4.41.26"
babel-loader "^8.2.2"
@@ -4496,10 +4518,10 @@
webpack-dev-middleware "^3.7.3"
webpack-virtual-modules "^0.2.2"
"@storybook/node-logger@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-6.3.0.tgz#ad64b23361beeb1864eb89337b7fe127a5d32929"
integrity sha512-gxvYOwDzHSYDTvnrwsyonCk88lRQ9gHrEvu3J8sM/0G/0br8g7G8+jSakKR8miE7urcwxd0uoYK+Y4KwJHkJpg==
"@storybook/node-logger@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-6.3.5.tgz#730e1ec384c30dbc9c5ab1896a558af3215b0635"
integrity sha512-mV6hXcMLdFPO7l1CFI+ycaoRDrbBxLBBSv8ivCX6acAhoheF0sLqJftpzwqlW3UYFEznVqD3PasqmO8I3f8dag==
dependencies:
"@types/npmlog" "^4.1.2"
chalk "^4.1.0"
@@ -4507,17 +4529,17 @@
npmlog "^4.1.2"
pretty-hrtime "^1.0.3"
"@storybook/postinstall@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/postinstall/-/postinstall-6.3.0.tgz#616999e96bc2f30e5deefd3c75415ce1dbc35cb3"
integrity sha512-QhhrhnB4yRdn5DGzygitccoKOYV+nKXWtQQm1TvEjMGrbZu57kI4X3TAsU4f3+wU8Xbdlfc8vhXpgCSzofRzGA==
"@storybook/postinstall@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/postinstall/-/postinstall-6.3.5.tgz#8fe88ffc4274ab447984fbbd435247d79e0d6f5d"
integrity sha512-ZR7xMZe1sVpwRudN67mLkjXeOYxSxvJ2MQJ+TnQcv0L0skTmF4GHLcuOs0aFpNa4rvo8VH9bqXNFVMDHj8R9Vw==
dependencies:
core-js "^3.8.2"
"@storybook/react-docgen-typescript-plugin@1.0.2-canary.3c70e01.0":
version "1.0.2-canary.3c70e01.0"
resolved "https://registry.yarnpkg.com/@storybook/react-docgen-typescript-plugin/-/react-docgen-typescript-plugin-1.0.2-canary.3c70e01.0.tgz#de49451523b86640463acc6028985ca11d8a63d1"
integrity sha512-go1LO+iM6qLGhgqvEoEpw339/kf2YBX86aG2JewWwlHCO0YyyYdlsdZd3KkB5MVtudyK7mtrcNDq0k/EIaB2JA==
"@storybook/react-docgen-typescript-plugin@1.0.2-canary.253f8c1.0":
version "1.0.2-canary.253f8c1.0"
resolved "https://registry.yarnpkg.com/@storybook/react-docgen-typescript-plugin/-/react-docgen-typescript-plugin-1.0.2-canary.253f8c1.0.tgz#f2da40e6aae4aa586c2fb284a4a1744602c3c7fa"
integrity sha512-mmoRG/rNzAiTbh+vGP8d57dfcR2aP+5/Ll03KKFyfy5FqWFm/Gh7u27ikx1I3LmVMI8n6jh5SdWMkMKon7/tDw==
dependencies:
debug "^4.1.1"
endent "^2.0.1"
@@ -4527,19 +4549,19 @@
react-docgen-typescript "^2.0.0"
tslib "^2.0.0"
"@storybook/react@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/react/-/react-6.3.0.tgz#e86ef1976fba2f0f80925278f196fb709df55eb4"
integrity sha512-GxK88Si9WQa16uUsUBhe6kRhSEZUrR/1ljP6QvLY+C5MyYJZ89DZPAbWnVo47SJCXhAlvJW83nSTSxnobn8RWA==
"@storybook/react@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/react/-/react-6.3.5.tgz#9fb3cdfda5f333084cb4e9b3ef952855d9483e0c"
integrity sha512-6k9rnDZ8/L1rwvlJMiLTQY6vcIHOnysFIYTzxBO32Ud0+RUh7UTyjp4JYoHu1LPLz3z+q77y7YgAhCvVNuMGQg==
dependencies:
"@babel/preset-flow" "^7.12.1"
"@babel/preset-react" "^7.12.10"
"@pmmmwh/react-refresh-webpack-plugin" "^0.4.3"
"@storybook/addons" "6.3.0"
"@storybook/core" "6.3.0"
"@storybook/core-common" "6.3.0"
"@storybook/node-logger" "6.3.0"
"@storybook/react-docgen-typescript-plugin" "1.0.2-canary.3c70e01.0"
"@storybook/addons" "6.3.5"
"@storybook/core" "6.3.5"
"@storybook/core-common" "6.3.5"
"@storybook/node-logger" "6.3.5"
"@storybook/react-docgen-typescript-plugin" "1.0.2-canary.253f8c1.0"
"@storybook/semver" "^7.3.2"
"@types/webpack-env" "^1.16.0"
babel-plugin-add-react-displayname "^0.0.5"
@@ -4556,13 +4578,13 @@
ts-dedent "^2.0.0"
webpack "4"
"@storybook/router@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/router/-/router-6.3.0.tgz#8b63773f11fe4c6749ccfda5d725c94275f0a459"
integrity sha512-RJcRVI6IqffLOU6k9GrlB3cXLLK5TRmFSIjwW3lEHVhj313e56uLRYTylT11aBf8bPEQ+MeQVe2sqQUBG3Ugng==
"@storybook/router@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/router/-/router-6.3.5.tgz#ab24a8e1416323413ee434f887b872a515be5da1"
integrity sha512-cLGYdmKhb3awE/Un4bNgTXY5tpPppYmoxuFNrOrWSgS3z4eed1W1YcMbL0EWbCTubz9zDAInD2gd0nhOS+HYeQ==
dependencies:
"@reach/router" "^1.3.4"
"@storybook/client-logger" "6.3.0"
"@storybook/client-logger" "6.3.5"
"@types/reach__router" "^1.3.7"
core-js "^3.8.2"
fast-deep-equal "^3.1.3"
@@ -4580,13 +4602,13 @@
core-js "^3.6.5"
find-up "^4.1.0"
"@storybook/source-loader@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/source-loader/-/source-loader-6.3.0.tgz#d1bbbb9c350c89f1207233d209694dcac5350e76"
integrity sha512-5LpqY5uu35Fg01D7Zu0xAT7ow6tcuHz+fkIxsGAJhzWovbV5NYl/BP8WSPv7TH+WjYve+RI2Xp6a9JFrgi9gpQ==
"@storybook/source-loader@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/source-loader/-/source-loader-6.3.5.tgz#66a57903e621fdd16ec07de5daf0f9e0bc7cfcf2"
integrity sha512-cIUVSItfaAot7MhiRkYoSM2/sYmRVGDTpx6ttUG1BVKwrb1/IcS5L6iMEyGTCyL70s3yLjxrhrLBW4QqiqKDXg==
dependencies:
"@storybook/addons" "6.3.0"
"@storybook/client-logger" "6.3.0"
"@storybook/addons" "6.3.5"
"@storybook/client-logger" "6.3.5"
"@storybook/csf" "0.0.1"
core-js "^3.8.2"
estraverse "^5.2.0"
@@ -4596,15 +4618,15 @@
prettier "~2.2.1"
regenerator-runtime "^0.13.7"
"@storybook/theming@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.3.0.tgz#4b6ef023631663d8e50f1348469b9a9641244cd0"
integrity sha512-Mtnq8qFv/TTtnl1sB6DGBCg/kJq7sR2e2uh/Uy2sHyksnhVITVJxEIFHSBo2L+IE6y0S2Oh6F9WdddWAO4Ao2g==
"@storybook/theming@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.3.5.tgz#673fa63e7723b651d0a400bf916169bfd3da8c81"
integrity sha512-TvgzzUvjJK1jJzqCiB4FUjvK9JZjPUsgYgAdbDuZX//N9zwbjR1cSR8eJWfuCDk0ECvQ6V+vnwnGQ5XmFqSJ1Q==
dependencies:
"@emotion/core" "^10.1.1"
"@emotion/is-prop-valid" "^0.8.6"
"@emotion/styled" "^10.0.27"
"@storybook/client-logger" "6.3.0"
"@storybook/client-logger" "6.3.5"
core-js "^3.8.2"
deep-object-diff "^1.1.0"
emotion-theming "^10.0.27"
@@ -4614,21 +4636,21 @@
resolve-from "^5.0.0"
ts-dedent "^2.0.0"
"@storybook/ui@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-6.3.0.tgz#3a17689c76d2ba2c3afadad2b5d0cf41abb74933"
integrity sha512-1f2+IB+WRrBSht538jNFSUDUxA8PpEOnDkJsE9PyOJEvskV/8POuCTjbpcvdBYFQ9hSq1oi3535mdbNiTivYTg==
"@storybook/ui@6.3.5":
version "6.3.5"
resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-6.3.5.tgz#b237c4ba69e8688a570f5ac967e9081c1260d59c"
integrity sha512-OCFnMCIhHgl9OwcX0WvPRgjIsoVUrhRxYLAsav67FI33qcjSCPDfN2aX+gp3VEJNGL+vmXRqLzMmhsfYaCQNpA==
dependencies:
"@emotion/core" "^10.1.1"
"@storybook/addons" "6.3.0"
"@storybook/api" "6.3.0"
"@storybook/channels" "6.3.0"
"@storybook/client-logger" "6.3.0"
"@storybook/components" "6.3.0"
"@storybook/core-events" "6.3.0"
"@storybook/router" "6.3.0"
"@storybook/addons" "6.3.5"
"@storybook/api" "6.3.5"
"@storybook/channels" "6.3.5"
"@storybook/client-logger" "6.3.5"
"@storybook/components" "6.3.5"
"@storybook/core-events" "6.3.5"
"@storybook/router" "6.3.5"
"@storybook/semver" "^7.3.2"
"@storybook/theming" "6.3.0"
"@storybook/theming" "6.3.5"
"@types/markdown-to-jsx" "^6.11.3"
copy-to-clipboard "^3.3.1"
core-js "^3.8.2"
@@ -7236,6 +7258,11 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
axe-core@^4.2.0:
version "4.3.1"
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.3.1.tgz#0c6a076e4a1c3e0544ba6a9479158f9be7a7928e"
integrity sha512-3WVgVPs/7OnKU3s+lqMtkv3wQlg3WxK1YifmpJSDO0E1aPBrZWlrrTO6cxRqCXLuX2aYgCljqXIQd0VnRidV0g==
axios@0.21.1:
version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
@@ -21878,15 +21905,15 @@ store2@^2.12.0:
resolved "https://registry.yarnpkg.com/store2/-/store2-2.12.0.tgz#e1f1b7e1a59b6083b2596a8d067f6ee88fd4d3cf"
integrity sha512-7t+/wpKLanLzSnQPX8WAcuLCCeuSHoWdQuh9SB3xD0kNOM38DNf+0Oa+wmvxmYueRzkmh6IcdKFtvTa+ecgPDw==
storybook-addon-outline@^1.3.3:
version "1.3.4"
resolved "https://registry.yarnpkg.com/storybook-addon-outline/-/storybook-addon-outline-1.3.4.tgz#4d90c262781db995312ca8bb6d4ba26028ff55b6"
integrity sha512-UNFansfJq1j5Z+GdB8/eoSck9A27VPm5HPG4LBnPKwvAmvjccVgY9mcbcG/ezF83RlrtCOKkfQ1NgOqz2NlGGg==
storybook-addon-outline@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/storybook-addon-outline/-/storybook-addon-outline-1.4.1.tgz#0a1b262b9c65df43fc63308a1fdbd4283c3d9458"
integrity sha512-Qvv9X86CoONbi+kYY78zQcTGmCgFaewYnOVR6WL7aOFJoW7TrLiIc/O4hH5X9PsEPZFqjfXEPUPENWVUQim6yw==
dependencies:
"@storybook/addons" "^6.3.0-beta.6"
"@storybook/api" "^6.3.0-beta.6"
"@storybook/components" "^6.3.0-beta.6"
"@storybook/core-events" "^6.3.0-beta.6"
"@storybook/addons" "^6.3.0"
"@storybook/api" "^6.3.0"
"@storybook/components" "^6.3.0"
"@storybook/core-events" "^6.3.0"
ts-dedent "^2.1.1"
storybook-dark-mode@1.0.8: