Merge branch 'v9.0.x' of https://github.com/grafana/grafana into v9.0.x
This commit is contained in:
+16
-11
@@ -4543,12 +4543,6 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
|
||||
],
|
||||
"public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "3"]
|
||||
],
|
||||
"public/app/features/dashboard/components/SubMenu/SubMenu.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
@@ -4849,7 +4843,8 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "24"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "25"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "26"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "27"]
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "27"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "28"]
|
||||
],
|
||||
"public/app/features/dashboard/state/TimeModel.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
@@ -6229,9 +6224,6 @@ exports[`better eslint`] = {
|
||||
"public/app/features/transformers/utils.ts:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
"public/app/features/users/UsersActionBar.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/features/users/__mocks__/userMocks.ts:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "1"],
|
||||
@@ -7861,7 +7853,20 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "10"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "11"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "12"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "13"]
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "13"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "14"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "15"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "16"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "17"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "18"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "19"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "20"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "21"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "22"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "23"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "24"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "25"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "26"]
|
||||
],
|
||||
"public/app/plugins/datasource/loki/datasource.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
<!-- 9.0.6 START -->
|
||||
|
||||
# 9.0.6 (2022-08-01)
|
||||
|
||||
### Features and enhancements
|
||||
|
||||
- **Access Control:** Allow org admins to invite new users to their organization. [#52904](https://github.com/grafana/grafana/pull/52904), [@IevaVasiljeva](https://github.com/IevaVasiljeva)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- **Grafana/toolkit:** Fix incorrect image and font generation for plugin builds. [#52927](https://github.com/grafana/grafana/pull/52927), [@academo](https://github.com/academo)
|
||||
- **Prometheus:** Fix adding of multiple values for regex operator. [#52978](https://github.com/grafana/grafana/pull/52978), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
- **UI/Card:** Fix card items always having pointer cursor. [#52809](https://github.com/grafana/grafana/pull/52809), [@gillesdemey](https://github.com/gillesdemey)
|
||||
|
||||
<!-- 9.0.6 END -->
|
||||
<!-- 9.0.5 START -->
|
||||
|
||||
# 9.0.5 (2022-07-26)
|
||||
|
||||
+1
-1
@@ -125,7 +125,7 @@ path = grafana.db
|
||||
# For "sqlite3" only. cache mode setting used for connecting to the database
|
||||
cache_mode = private
|
||||
|
||||
# For "mysql" only if lockingMigration feature toggle is set. How many seconds to wait before failing to lock the database for the migrations, default is 0.
|
||||
# For "mysql" only if migrationLocking feature toggle is set. How many seconds to wait before failing to lock the database for the migrations, default is 0.
|
||||
locking_attempt_timeout_sec = 0
|
||||
|
||||
#################################### Cache server #############################
|
||||
|
||||
+1
-1
@@ -126,7 +126,7 @@
|
||||
# For "sqlite3" only. cache mode setting used for connecting to the database. (private, shared)
|
||||
;cache_mode = private
|
||||
|
||||
# For "mysql" only if lockingMigration feature toggle is set. How many seconds to wait before failing to lock the database for the migrations, default is 0.
|
||||
# For "mysql" only if migrationLocking feature toggle is set. How many seconds to wait before failing to lock the database for the migrations, default is 0.
|
||||
;locking_attempt_timeout_sec = 0
|
||||
|
||||
################################### Data sources #########################
|
||||
|
||||
@@ -191,4 +191,4 @@ import { Plural } from "@lingui/macro"
|
||||
|
||||
## Documentation
|
||||
|
||||
[Grafana's documentation](https://grafana.com/docs/grafana/latest/) is not yet open for translation and should be authored in English only.
|
||||
[Grafana's documentation](https://grafana.com/docs/grafana/latest/) is not yet open for translation and should be authored in American English only.
|
||||
@@ -14,24 +14,18 @@ weight: 700
|
||||
|
||||
# API keys
|
||||
|
||||
API keys can be used to interact with Grafana HTTP APIs.
|
||||
|
||||
We recommend using service accounts instead of API keys if you are on Grafana 8.5+, for more information refer to [About service accounts]({{< relref "../service-accounts/about-service-accounts/#" >}}).
|
||||
|
||||
{{< section >}}
|
||||
|
||||
## About API keys
|
||||
|
||||
An API key is a randomly generated string that external systems use to interact with Grafana HTTP APIs.
|
||||
|
||||
When you create an API key, you specify a **Role** that determines the permissions associated with the API key. Role permissions control that actions the API key can perform on Grafana resources. For more information about creating API keys, refer to [Create an API key]({{< relref "create-api-key/#" >}}).
|
||||
When you create an API key, you specify a **Role** that determines the permissions associated with the API key. Role permissions control that actions the API key can perform on Grafana resources.
|
||||
|
||||
> **Note:** If you use Grafana v8.5 or newer, use service accounts instead of API keys. For more information, refer to [Service accounts]({{< relref "../service-accounts/" >}}).
|
||||
|
||||
{{< section >}}
|
||||
|
||||
## Create an API key
|
||||
|
||||
Create an API key when you want to manage your computed workload with a user.
|
||||
|
||||
For more information about API keys, refer to [About API keys in Grafana]({{< relref "about-api-keys/" >}}).
|
||||
|
||||
This topic shows you how to create an API key using the Grafana UI. You can also create an API key using the Grafana HTTP API. For more information about creating API keys via the API, refer to [Create API key via API]({{< relref "../../developers/http_api/create-api-tokens-for-org/#how-to-create-a-new-organization-and-an-api-token" >}}).
|
||||
|
||||
### Before you begin:
|
||||
|
||||
@@ -18,7 +18,9 @@ weight: 999
|
||||
|
||||
# Deprecated Application Insights and Insights Analytics
|
||||
|
||||
Application Insights and Insights Analytics are two ways to query the same Azure Application Insights data, which can also be queried from Metrics and Logs. In Grafana 8.0, Application Insights and Insights Analytics are deprecated and made read-only in favor of querying this data through Metrics and Logs. Existing queries will continue to work, but you cannot edit them. New panels are not able to use Application Insights or Insights Analytics.
|
||||
Application Insights and Insights Analytics are two ways to query the same Azure Application Insights data, which can also be queried from Metrics and Logs. In Grafana 8.0, Application Insights and Insights Analytics were deprecated and made read-only in favor of querying this data through Metrics and Logs.
|
||||
|
||||
These query methods were completely removed in Grafana 9.0.
|
||||
|
||||
Azure Monitor Metrics and Azure Monitor Logs do not use Application Insights API keys, so make sure the data source is configured with an Azure AD app registration that has access to Application Insights.
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ weight: 10000
|
||||
Here you can find detailed release notes that list everything that is included in every release as well as notices
|
||||
about deprecations, breaking changes as well as changes that relate to plugin development.
|
||||
|
||||
- [Release notes for 9.0.6]({{< relref "release-notes-9-0-6" >}})
|
||||
- [Release notes for 9.0.5]({{< relref "release-notes-9-0-5" >}})
|
||||
- [Release notes for 9.0.4]({{< relref "release-notes-9-0-4" >}})
|
||||
- [Release notes for 9.0.3]({{< relref "release-notes-9-0-3" >}})
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
+++
|
||||
title = "Release notes for Grafana 9.0.6"
|
||||
hide_menu = true
|
||||
+++
|
||||
|
||||
<!-- Auto generated by update changelog github action -->
|
||||
|
||||
# Release notes for Grafana 9.0.6
|
||||
|
||||
### Features and enhancements
|
||||
|
||||
- **Access Control:** Allow org admins to invite new users to their organization. [#52904](https://github.com/grafana/grafana/pull/52904), [@IevaVasiljeva](https://github.com/IevaVasiljeva)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- **Grafana/toolkit:** Fix incorrect image and font generation for plugin builds. [#52927](https://github.com/grafana/grafana/pull/52927), [@academo](https://github.com/academo)
|
||||
- **Prometheus:** Fix adding of multiple values for regex operator. [#52978](https://github.com/grafana/grafana/pull/52978), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
- **UI/Card:** Fix card items always having pointer cursor. [#52809](https://github.com/grafana/grafana/pull/52809), [@gillesdemey](https://github.com/gillesdemey)
|
||||
@@ -324,7 +324,7 @@ Sets the maximum amount of time a connection may be reused. The default is 14400
|
||||
|
||||
### locking_attempt_timeout_sec
|
||||
|
||||
For "mysql", if `lockingMigration` feature toggle is set, specify the time (in seconds) to wait before failing to lock the database for the migrations. Default is 0.
|
||||
For "mysql", if the `migrationLocking` feature toggle is set, specify the time (in seconds) to wait before failing to lock the database for the migrations. Default is 0.
|
||||
|
||||
### log_queries
|
||||
|
||||
@@ -1204,11 +1204,11 @@ The interval string is a possibly signed sequence of decimal numbers, followed b
|
||||
|
||||
### ha_listen_address
|
||||
|
||||
Listen address/hostname and port to receive unified alerting messages for other Grafana instances. The port is used for both TCP and UDP. It is assumed other Grafana instances are also running on the same port. The default value is `0.0.0.0:9094`.
|
||||
Listen IP address and port to receive unified alerting messages for other Grafana instances. The port is used for both TCP and UDP. It is assumed other Grafana instances are also running on the same port. The default value is `0.0.0.0:9094`.
|
||||
|
||||
### ha_advertise_address
|
||||
|
||||
Explicit address/hostname and port to advertise other Grafana instances. The port is used for both TCP and UDP.
|
||||
Explicit IP address and port to advertise other Grafana instances. The port is used for both TCP and UDP.
|
||||
|
||||
### ha_peers
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ This panel visualization allows you to graph categorical data.
|
||||
|
||||
## Supported data formats
|
||||
|
||||
Only one data frame is supported and it needs to have at least one string field that will be used as the category for an X or Y axis and one or more numerical fields.
|
||||
Only one data frame is supported and it must have at least one string field that will be used as the category for an X or Y axis and one or more numerical fields.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -107,6 +107,8 @@ Gradient color is generated based on the hue of the line color.
|
||||
|
||||
Choose which of the [standard calculations]({{< relref "../panels/calculation-types/" >}}) to show in the legend. You can have more than one.
|
||||
|
||||
For more information about the legend, refer to [Configure a legend](../configure-legend/).
|
||||
|
||||
## Text size
|
||||
|
||||
Enter a **Value** to change the size of the text on your bar chart.
|
||||
|
||||
+5
-17
@@ -1,9 +1,9 @@
|
||||
---
|
||||
aliases:
|
||||
- /docs/grafana/latest/panels/working-with-panels/configure-legend/
|
||||
- /docs/sources/panels/working-with-panels/configure-legend/
|
||||
- /docs/grafana/latest/visualizations/configure-legend/
|
||||
title: Configure a legend
|
||||
weight: 70
|
||||
weight: 1300
|
||||
---
|
||||
|
||||
# Configure a legend
|
||||
@@ -41,13 +41,7 @@ This topic currently applies to the following visualizations:
|
||||
|
||||
As way to add more context to a visualization, you can add series data values to a legend. You can add as many values as you'd like; after you apply your changes, you can horizontally scroll the legend to see all values.
|
||||
|
||||
### Before you begin
|
||||
|
||||
- Add a panel
|
||||
|
||||
**To add values to a legend**:
|
||||
|
||||
1. Open a panel.
|
||||
1. Edit a panel.
|
||||
|
||||
1. In the panel display options pane, locate the **Legend** section.
|
||||
|
||||
@@ -61,13 +55,7 @@ As way to add more context to a visualization, you can add series data values to
|
||||
|
||||
By default, Grafana specifies the color of your series data, which you can change.
|
||||
|
||||
### Before you begin
|
||||
|
||||
- Add a panel
|
||||
|
||||
**To change a series color, perform the following steps**:
|
||||
|
||||
1. Open the panel.
|
||||
1. Edit a panel.
|
||||
|
||||
1. In the legend, click the color bar associated with the series.
|
||||
|
||||
@@ -79,7 +67,7 @@ By default, Grafana specifies the color of your series data, which you can chang
|
||||
|
||||
## Sort series
|
||||
|
||||
Change legend mode to **Table** and choose [calculations]({{< relref "../calculation-types/" >}}) to be displayed in the legend. Click the calculation name header in the legend table to sort the values in the table in ascending or descending order.
|
||||
You can change legend mode to **Table** and choose [calculations]({{< relref "../../panels/calculation-types/" >}}) to be displayed in the legend. Click the calculation name header in the legend table to sort the values in the table in ascending or descending order.
|
||||
|
||||
The sort order affects the positions of the bars in the Bar chart panel as well as the order of stacked series in the Time series and Bar chart panels.
|
||||
|
||||
@@ -80,5 +80,7 @@ The following example shows a pie chart with **Name** and **Percent** labels dis
|
||||
|
||||
Select values to display in the legend. You can select more than one.
|
||||
|
||||
**Percent -** The percentage of the whole.
|
||||
**Value -** The raw numerical value.
|
||||
- **Percent:** The percentage of the whole.
|
||||
- **Value:** The raw numerical value.
|
||||
|
||||
For more information about the legend, refer to [Configure a legend](../configure-legend/).
|
||||
|
||||
@@ -33,6 +33,8 @@ These options are available whether you are graphing your time series as lines,
|
||||
|
||||
Choose which of the [standard calculations]({{< relref "../../panels/calculation-types/" >}}) to show in the legend. You can have more than one.
|
||||
|
||||
For more information about the legend, refer to [Configure a legend](../configure-legend/).
|
||||
|
||||
## Graph styles
|
||||
|
||||
Use these options to choose how to display your time series data.
|
||||
|
||||
+1
-1
@@ -4,5 +4,5 @@
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "9.0.5"
|
||||
"version": "9.0.6"
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
"license": "AGPL-3.0-only",
|
||||
"private": true,
|
||||
"name": "grafana",
|
||||
"version": "9.0.5",
|
||||
"version": "9.0.6",
|
||||
"repository": "github:grafana/grafana",
|
||||
"scripts": {
|
||||
"api-tests": "jest --notify --watch --config=devenv/e2e-api-tests/jest.js",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/data",
|
||||
"version": "9.0.5",
|
||||
"version": "9.0.6",
|
||||
"description": "Grafana Data Library",
|
||||
"keywords": [
|
||||
"typescript"
|
||||
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "6.0.0",
|
||||
"@grafana/schema": "9.0.5",
|
||||
"@grafana/schema": "9.0.6",
|
||||
"@types/d3-interpolate": "^1.4.0",
|
||||
"d3-interpolate": "1.4.0",
|
||||
"date-fns": "2.28.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/e2e-selectors",
|
||||
"version": "9.0.5",
|
||||
"version": "9.0.6",
|
||||
"description": "Grafana End-to-End Test Selectors Library",
|
||||
"keywords": [
|
||||
"cli",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/e2e",
|
||||
"version": "9.0.5",
|
||||
"version": "9.0.6",
|
||||
"description": "Grafana End-to-End Test Library",
|
||||
"keywords": [
|
||||
"cli",
|
||||
@@ -48,7 +48,7 @@
|
||||
"@babel/core": "7.17.8",
|
||||
"@babel/preset-env": "7.17.10",
|
||||
"@cypress/webpack-preprocessor": "5.11.1",
|
||||
"@grafana/e2e-selectors": "9.0.5",
|
||||
"@grafana/e2e-selectors": "9.0.6",
|
||||
"@grafana/tsconfig": "^1.2.0-rc1",
|
||||
"@mochajs/json-file-reporter": "^1.2.0",
|
||||
"babel-loader": "8.2.5",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/runtime",
|
||||
"version": "9.0.5",
|
||||
"version": "9.0.6",
|
||||
"description": "Grafana Runtime Library",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -22,9 +22,9 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grafana/data": "9.0.5",
|
||||
"@grafana/e2e-selectors": "9.0.5",
|
||||
"@grafana/ui": "9.0.5",
|
||||
"@grafana/data": "9.0.6",
|
||||
"@grafana/e2e-selectors": "9.0.6",
|
||||
"@grafana/ui": "9.0.6",
|
||||
"@sentry/browser": "6.19.7",
|
||||
"history": "4.10.1",
|
||||
"lodash": "4.17.21",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/schema",
|
||||
"version": "9.0.5",
|
||||
"version": "9.0.6",
|
||||
"description": "Grafana Schema Library",
|
||||
"keywords": [
|
||||
"typescript"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/toolkit",
|
||||
"version": "9.0.5",
|
||||
"version": "9.0.6",
|
||||
"description": "Grafana Toolkit",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -38,10 +38,10 @@
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
"@babel/preset-react": "^7.16.7",
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"@grafana/data": "9.0.5",
|
||||
"@grafana/data": "9.0.6",
|
||||
"@grafana/eslint-config": "^4.0.0",
|
||||
"@grafana/tsconfig": "^1.2.0-rc1",
|
||||
"@grafana/ui": "9.0.5",
|
||||
"@grafana/ui": "9.0.6",
|
||||
"@jest/core": "27.5.1",
|
||||
"@types/command-exists": "^1.2.0",
|
||||
"@types/eslint": "8.4.1",
|
||||
|
||||
@@ -5,7 +5,7 @@ import rimrafCallback from 'rimraf';
|
||||
import { promisify } from 'util';
|
||||
|
||||
import { getPluginId } from '../../config/utils/getPluginId';
|
||||
import { getPluginJson } from '../../config/utils/pluginValidation';
|
||||
import { assertRootUrlIsValid, getPluginJson } from '../../config/utils/pluginValidation';
|
||||
import {
|
||||
getJobFolder,
|
||||
writeJobStats,
|
||||
@@ -140,7 +140,8 @@ const packagePluginRunner: TaskRunner<PluginCIOptions> = async ({ signatureType,
|
||||
if (signatureType) {
|
||||
manifest.signatureType = signatureType;
|
||||
}
|
||||
if (rootUrls) {
|
||||
if (rootUrls && rootUrls.length > 0) {
|
||||
rootUrls.forEach(assertRootUrlIsValid);
|
||||
manifest.rootUrls = rootUrls;
|
||||
}
|
||||
const signedManifest = await signManifest(manifest);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import path from 'path';
|
||||
|
||||
import { assertRootUrlIsValid } from '../../config/utils/pluginValidation';
|
||||
import { buildManifest, signManifest, saveManifest } from '../../plugins/manifest';
|
||||
|
||||
import { getToolkitVersion } from './plugin.utils';
|
||||
@@ -21,7 +22,8 @@ const pluginSignRunner: TaskRunner<PluginSignOptions> = async ({ signatureType,
|
||||
if (signatureType) {
|
||||
manifest.signatureType = signatureType;
|
||||
}
|
||||
if (rootUrls) {
|
||||
if (rootUrls && rootUrls.length > 0) {
|
||||
rootUrls.forEach(assertRootUrlIsValid);
|
||||
manifest.rootUrls = rootUrls;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,3 +36,11 @@ export const getPluginJson = (path: string): PluginMeta => {
|
||||
|
||||
return pluginJson as PluginMeta;
|
||||
};
|
||||
|
||||
export const assertRootUrlIsValid = (rootUrl: string) => {
|
||||
try {
|
||||
new URL(rootUrl);
|
||||
} catch (err) {
|
||||
throw new Error(`${rootUrl} is not a valid URL`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { getPluginId } from '../utils/getPluginId';
|
||||
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
|
||||
const supportedExtensions = ['css', 'scss', 'less', 'sass'];
|
||||
@@ -109,7 +111,7 @@ export const getFileLoaders = () => {
|
||||
test: /\.(png|jpe?g|gif|svg)$/,
|
||||
type: 'asset/resource',
|
||||
generator: {
|
||||
publicPath: `img/`,
|
||||
publicPath: `public/plugins/${getPluginId()}/img/`,
|
||||
outputPath: 'img/',
|
||||
},
|
||||
},
|
||||
@@ -117,7 +119,7 @@ export const getFileLoaders = () => {
|
||||
test: /\.(woff|woff2|eot|ttf|otf)(\?v=\d+\.\d+\.\d+)?$/,
|
||||
type: 'asset/resource',
|
||||
generator: {
|
||||
publicPath: `fonts/`,
|
||||
publicPath: `public/plugins/${getPluginId()}/fonts/`,
|
||||
outputPath: 'fonts/',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/ui",
|
||||
"version": "9.0.5",
|
||||
"version": "9.0.6",
|
||||
"description": "Grafana Components Library",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -33,9 +33,9 @@
|
||||
"@emotion/css": "11.9.0",
|
||||
"@emotion/react": "11.9.0",
|
||||
"@grafana/aws-sdk": "0.0.36",
|
||||
"@grafana/data": "9.0.5",
|
||||
"@grafana/e2e-selectors": "9.0.5",
|
||||
"@grafana/schema": "9.0.5",
|
||||
"@grafana/data": "9.0.6",
|
||||
"@grafana/e2e-selectors": "9.0.6",
|
||||
"@grafana/schema": "9.0.6",
|
||||
"@grafana/slate-react": "0.22.10-grafana",
|
||||
"@monaco-editor/react": "4.3.1",
|
||||
"@popperjs/core": "2.11.5",
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { PageToolbar } from '..';
|
||||
|
||||
describe('PageToolbar', () => {
|
||||
it('renders left items when title is not set', () => {
|
||||
const leftItemContent = 'Left Item!';
|
||||
render(<PageToolbar leftItems={[<div key="left-item">{leftItemContent}</div>]} />);
|
||||
|
||||
expect(screen.getByText(leftItemContent)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -61,12 +61,6 @@ export const PageToolbar: FC<Props> = React.memo(
|
||||
className
|
||||
);
|
||||
|
||||
const leftItemChildren = leftItems?.map((child, index) => (
|
||||
<div className={styles.leftActionItem} key={index}>
|
||||
{child}
|
||||
</div>
|
||||
));
|
||||
|
||||
const titleEl = (
|
||||
<>
|
||||
<span className={styles.noLinkTitle}>{title}</span>
|
||||
@@ -112,22 +106,29 @@ export const PageToolbar: FC<Props> = React.memo(
|
||||
</>
|
||||
)}
|
||||
|
||||
{title && (
|
||||
{(title || leftItems?.length) && (
|
||||
<div className={styles.titleWrapper}>
|
||||
<h1 className={styles.h1Styles}>
|
||||
{titleHref ? (
|
||||
<Link
|
||||
aria-label="Search dashboard by name"
|
||||
className={cx(styles.titleText, styles.titleLink)}
|
||||
href={titleHref}
|
||||
>
|
||||
{titleEl}
|
||||
</Link>
|
||||
) : (
|
||||
<div className={styles.titleText}>{titleEl}</div>
|
||||
)}
|
||||
</h1>
|
||||
{leftItemChildren}
|
||||
{title && (
|
||||
<h1 className={styles.h1Styles}>
|
||||
{titleHref ? (
|
||||
<Link
|
||||
aria-label="Search dashboard by name"
|
||||
className={cx(styles.titleText, styles.titleLink)}
|
||||
href={titleHref}
|
||||
>
|
||||
{titleEl}
|
||||
</Link>
|
||||
) : (
|
||||
<div className={styles.titleText}>{titleEl}</div>
|
||||
)}
|
||||
</h1>
|
||||
)}
|
||||
|
||||
{leftItems?.map((child, index) => (
|
||||
<div className={styles.leftActionItem} key={index}>
|
||||
{child}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jaegertracing/jaeger-ui-components",
|
||||
"version": "9.0.5",
|
||||
"version": "9.0.6",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -28,10 +28,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/css": "11.9.0",
|
||||
"@grafana/data": "9.0.5",
|
||||
"@grafana/e2e-selectors": "9.0.5",
|
||||
"@grafana/runtime": "9.0.5",
|
||||
"@grafana/ui": "9.0.5",
|
||||
"@grafana/data": "9.0.6",
|
||||
"@grafana/e2e-selectors": "9.0.6",
|
||||
"@grafana/runtime": "9.0.6",
|
||||
"@grafana/ui": "9.0.6",
|
||||
"chance": "^1.0.10",
|
||||
"classnames": "^2.2.5",
|
||||
"combokeys": "^3.0.0",
|
||||
|
||||
@@ -153,14 +153,17 @@ func (hs *HTTPServer) CreateDashboardSnapshot(c *models.ReqContext) response.Res
|
||||
func (hs *HTTPServer) GetDashboardSnapshot(c *models.ReqContext) response.Response {
|
||||
key := web.Params(c.Req)[":key"]
|
||||
if len(key) == 0 {
|
||||
return response.Error(404, "Snapshot not found", nil)
|
||||
return response.Error(http.StatusBadRequest, "Empty snapshot key", nil)
|
||||
}
|
||||
|
||||
query := &models.GetDashboardSnapshotQuery{Key: key}
|
||||
|
||||
err := hs.DashboardsnapshotsService.GetDashboardSnapshot(c.Req.Context(), query)
|
||||
if err != nil {
|
||||
return response.Error(500, "Failed to get dashboard snapshot", err)
|
||||
if errors.Is(err, models.ErrDashboardSnapshotNotFound) {
|
||||
return response.Error(http.StatusNotFound, "Failed to find dashboard snapshot", err)
|
||||
}
|
||||
return response.Error(http.StatusInternalServerError, "Failed to get dashboard snapshot", err)
|
||||
}
|
||||
|
||||
snapshot := query.Result
|
||||
@@ -227,7 +230,10 @@ func (hs *HTTPServer) DeleteDashboardSnapshotByDeleteKey(c *models.ReqContext) r
|
||||
query := &models.GetDashboardSnapshotQuery{DeleteKey: key}
|
||||
err := hs.DashboardsnapshotsService.GetDashboardSnapshot(c.Req.Context(), query)
|
||||
if err != nil {
|
||||
return response.Error(500, "Failed to get dashboard snapshot", err)
|
||||
if errors.Is(err, models.ErrDashboardSnapshotNotFound) {
|
||||
return response.Error(http.StatusNotFound, "Failed to find dashboard snapshot", err)
|
||||
}
|
||||
return response.Error(http.StatusInternalServerError, "Failed to get dashboard snapshot", err)
|
||||
}
|
||||
|
||||
if query.Result.External {
|
||||
@@ -260,7 +266,10 @@ func (hs *HTTPServer) DeleteDashboardSnapshot(c *models.ReqContext) response.Res
|
||||
|
||||
err := hs.DashboardsnapshotsService.GetDashboardSnapshot(c.Req.Context(), query)
|
||||
if err != nil {
|
||||
return response.Error(500, "Failed to get dashboard snapshot", err)
|
||||
if errors.Is(err, models.ErrDashboardSnapshotNotFound) {
|
||||
return response.Error(http.StatusNotFound, "Failed to find dashboard snapshot", err)
|
||||
}
|
||||
return response.Error(http.StatusInternalServerError, "Failed to get dashboard snapshot", err)
|
||||
}
|
||||
if query.Result == nil {
|
||||
return response.Error(404, "Failed to get dashboard snapshot", nil)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -229,3 +230,71 @@ func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) {
|
||||
}, sqlmock)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetDashboardSnapshotNotFound(t *testing.T) {
|
||||
sqlmock := mockstore.NewSQLStoreMock()
|
||||
sqlmock.ExpectedTeamsByUser = []*models.TeamDTO{}
|
||||
sqlmock.ExpectedError = models.ErrDashboardSnapshotNotFound
|
||||
hs := &HTTPServer{DashboardsnapshotsService: &dashboardsnapshots.Service{SQLStore: sqlmock}}
|
||||
|
||||
loggedInUserScenarioWithRole(t,
|
||||
"GET /snapshots/{key} should return 404 when the snapshot does not exist", "GET",
|
||||
"/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
sc.handlerFunc = hs.GetDashboardSnapshot
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{"key": "12345"}).exec()
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, sc.resp.Code)
|
||||
}, sqlmock)
|
||||
|
||||
loggedInUserScenarioWithRole(t,
|
||||
"DELETE /snapshots/{key} should return 404 when the snapshot does not exist", "DELETE",
|
||||
"/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
sc.handlerFunc = hs.DeleteDashboardSnapshot
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec()
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, sc.resp.Code)
|
||||
}, sqlmock)
|
||||
|
||||
loggedInUserScenarioWithRole(t,
|
||||
"GET /snapshots-delete/{deleteKey} should return 404 when the snapshot does not exist", "DELETE",
|
||||
"/api/snapshots-delete/12345", "/api/snapshots-delete/:deleteKey", models.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
sc.handlerFunc = hs.DeleteDashboardSnapshotByDeleteKey
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"deleteKey": "12345"}).exec()
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, sc.resp.Code)
|
||||
}, sqlmock)
|
||||
}
|
||||
|
||||
func TestGetDashboardSnapshotFailure(t *testing.T) {
|
||||
sqlmock := mockstore.NewSQLStoreMock()
|
||||
sqlmock.ExpectedTeamsByUser = []*models.TeamDTO{}
|
||||
sqlmock.ExpectedError = errors.New("something went wrong")
|
||||
hs := &HTTPServer{DashboardsnapshotsService: &dashboardsnapshots.Service{SQLStore: sqlmock}}
|
||||
|
||||
loggedInUserScenarioWithRole(t,
|
||||
"GET /snapshots/{key} should return 404 when the snapshot does not exist", "GET",
|
||||
"/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
sc.handlerFunc = hs.GetDashboardSnapshot
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{"key": "12345"}).exec()
|
||||
|
||||
assert.Equal(t, http.StatusInternalServerError, sc.resp.Code)
|
||||
}, sqlmock)
|
||||
|
||||
loggedInUserScenarioWithRole(t,
|
||||
"DELETE /snapshots/{key} should return 404 when the snapshot does not exist", "DELETE",
|
||||
"/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
sc.handlerFunc = hs.DeleteDashboardSnapshot
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec()
|
||||
|
||||
assert.Equal(t, http.StatusInternalServerError, sc.resp.Code)
|
||||
}, sqlmock)
|
||||
|
||||
loggedInUserScenarioWithRole(t,
|
||||
"GET /snapshots-delete/{deleteKey} should return 404 when the snapshot does not exist", "DELETE",
|
||||
"/api/snapshots-delete/12345", "/api/snapshots-delete/:deleteKey", models.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
sc.handlerFunc = hs.DeleteDashboardSnapshotByDeleteKey
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"deleteKey": "12345"}).exec()
|
||||
|
||||
assert.Equal(t, http.StatusInternalServerError, sc.resp.Code)
|
||||
}, sqlmock)
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
//
|
||||
// Responses:
|
||||
// 200: snapshotResponse
|
||||
// 400: badRequestError
|
||||
// 404: notFoundError
|
||||
// 500: internalServerError
|
||||
|
||||
|
||||
@@ -27,7 +27,12 @@ SELECT DISTINCT
|
||||
, (SELECT COUNT(connection_id) FROM ` + models.LibraryElementConnectionTableName + ` WHERE element_id = le.id AND kind=1) AS connected_dashboards`
|
||||
)
|
||||
|
||||
const deleteInvalidConnections = "DELETE FROM library_element_connection WHERE connection_id IN (SELECT connection_id as id FROM library_element_connection WHERE element_id=? EXCEPT SELECT id from dashboard)"
|
||||
const deleteInvalidConnections = `
|
||||
DELETE FROM library_element_connection
|
||||
WHERE connection_id IN (
|
||||
SELECT connection_id as id FROM library_element_connection
|
||||
WHERE element_id=? AND connection_id NOT IN (SELECT id as connection_id from dashboard)
|
||||
)`
|
||||
|
||||
func getFromLibraryElementDTOWithMeta(dialect migrator.Dialect) string {
|
||||
user := dialect.Quote("user")
|
||||
@@ -589,7 +594,7 @@ func (l *LibraryElementService) getConnections(c context.Context, signedInUser *
|
||||
return connections, err
|
||||
}
|
||||
|
||||
//getElementsForDashboardID gets all elements for a specific dashboard
|
||||
// getElementsForDashboardID gets all elements for a specific dashboard
|
||||
func (l *LibraryElementService) getElementsForDashboardID(c context.Context, dashboardID int64) (map[string]LibraryElementDTO, error) {
|
||||
libraryElementMap := make(map[string]LibraryElementDTO)
|
||||
err := l.SQLStore.WithDbSession(c, func(session *sqlstore.DBSession) error {
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
)
|
||||
|
||||
var searchRegex = regexp.MustCompile(`\{(\w+)\}`)
|
||||
|
||||
var errInvalidRecipientFormat = errors.New("invalid recipient (datasource) identifier format. Only integer is expected")
|
||||
var NotImplementedResp = ErrResp(http.StatusNotImplemented, errors.New("endpoint not implemented"), "")
|
||||
|
||||
func toMacaronPath(path string) string {
|
||||
@@ -37,7 +37,8 @@ func toMacaronPath(path string) string {
|
||||
|
||||
func backendTypeByUID(ctx *models.ReqContext, cache datasources.CacheService) (apimodels.Backend, error) {
|
||||
datasourceUID := web.Params(ctx.Req)[":DatasourceUID"]
|
||||
if ds, err := cache.GetDatasourceByUID(ctx.Req.Context(), datasourceUID, ctx.SignedInUser, ctx.SkipCache); err == nil {
|
||||
ds, err := cache.GetDatasourceByUID(ctx.Req.Context(), datasourceUID, ctx.SignedInUser, ctx.SkipCache)
|
||||
if err == nil {
|
||||
switch ds.Type {
|
||||
case "loki", "prometheus":
|
||||
return apimodels.LoTexRulerBackend, nil
|
||||
@@ -47,7 +48,7 @@ func backendTypeByUID(ctx *models.ReqContext, cache datasources.CacheService) (a
|
||||
return 0, fmt.Errorf("unexpected backend type (%v)", ds.Type)
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("unexpected backend type (%v)", datasourceUID)
|
||||
return 0, errors.New("no datasource was found matching the given UID")
|
||||
}
|
||||
|
||||
// macaron unsafely asserts the http.ResponseWriter is an http.CloseNotifier, which will panic.
|
||||
@@ -100,7 +101,7 @@ func (p *AlertingProxy) withReq(
|
||||
if datasourceID != "" {
|
||||
recipient, err := strconv.ParseInt(web.Params(ctx.Req)[":DatasourceID"], 10, 64)
|
||||
if err != nil {
|
||||
return ErrResp(http.StatusBadRequest, err, "DatasourceID is invalid")
|
||||
return ErrResp(http.StatusBadRequest, errInvalidRecipientFormat, "")
|
||||
}
|
||||
|
||||
p.DataProxy.ProxyDatasourceRequestWithID(newCtx, recipient)
|
||||
|
||||
@@ -658,12 +658,14 @@ func addConfigData(frames data.Frames, dl string, unit string) data.Frames {
|
||||
if frames[i].Fields[1].Config == nil {
|
||||
frames[i].Fields[1].Config = &data.FieldConfig{}
|
||||
}
|
||||
deepLink := data.DataLink{
|
||||
Title: "View in Metrics Explorer",
|
||||
TargetBlank: true,
|
||||
URL: dl,
|
||||
if len(dl) > 0 {
|
||||
deepLink := data.DataLink{
|
||||
Title: "View in Metrics Explorer",
|
||||
TargetBlank: true,
|
||||
URL: dl,
|
||||
}
|
||||
frames[i].Fields[1].Config.Links = append(frames[i].Fields[1].Config.Links, deepLink)
|
||||
}
|
||||
frames[i].Fields[1].Config.Links = append(frames[i].Fields[1].Config.Links, deepLink)
|
||||
if len(unit) > 0 {
|
||||
if val, ok := cloudMonitoringUnitMappings[unit]; ok {
|
||||
frames[i].Fields[1].Config.Unit = val
|
||||
|
||||
@@ -459,6 +459,19 @@ func TestTimeSeriesFilter(t *testing.T) {
|
||||
}, *res.Frames[0].Meta)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when data comes from a slo query, it should skip the link", func(t *testing.T) {
|
||||
data, err := loadTestFile("./test-data/3-series-response-distribution-exponential.json")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, len(data.TimeSeries))
|
||||
|
||||
res := &backend.DataResponse{}
|
||||
query := &cloudMonitoringTimeSeriesFilter{Params: url.Values{}, Slo: "yes"}
|
||||
err = query.parseResponse(res, data, "")
|
||||
require.NoError(t, err)
|
||||
frames := res.Frames
|
||||
assert.Equal(t, len(frames[0].Fields[1].Config.Links), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func loadTestFile(path string) (cloudMonitoringResponse, error) {
|
||||
|
||||
@@ -398,6 +398,7 @@ var metricsMap = map[string][]string{
|
||||
"AWS/Rekognition": {"DetectedFaceCount", "DetectedLabelCount", "ResponseTime", "ServerErrorCount", "SuccessfulRequestCount", "ThrottledCount", "UserErrorCount"},
|
||||
"AWS/Cassandra": {"AccountMaxReads", "AccountMaxTableLevelReads", "AccountMaxTableLevelWrites", "AccountMaxWrites", "AccountProvisionedReadCapacityUtilization", "AccountProvisionedWriteCapacityUtilization", "ConditionalCheckFailedRequests", "ConsumedReadCapacityUnits", "ConsumedWriteCapacityUnits", "MaxProvisionedTableReadCapacityUtilization", "MaxProvisionedTableWriteCapacityUtilization", "ReturnedItemCount", "ReturnedItemCountBySelect", "SuccessfulRequestCount", "SuccessfulRequestLatency", "SystemErrors", "UserErrors"},
|
||||
"AWS/AmplifyHosting": {"Requests", "BytesDownloaded", "BytesUploaded", "4XXErrors", "5XXErrors", "Latency"},
|
||||
"CloudWatchSynthetics": {"SuccessPercent", "Duration", "2xx", "4xx", "5xx", "Failed", "Failed requests", "VisualMonitoringSuccessPercent", "VisualMonitoringTotalComparisons"},
|
||||
}
|
||||
|
||||
var dimensionsMap = map[string][]string{
|
||||
@@ -513,6 +514,7 @@ var dimensionsMap = map[string][]string{
|
||||
"AWS/Rekognition": {},
|
||||
"AWS/Cassandra": {"Keyspace", "Operation", "TableName"},
|
||||
"AWS/AmplifyHosting": {"App"},
|
||||
"CloudWatchSynthetics": {"CanaryName"},
|
||||
}
|
||||
|
||||
// Known AWS regions.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@grafana-plugins/input-datasource",
|
||||
"version": "9.0.5",
|
||||
"version": "9.0.6",
|
||||
"description": "Input Datasource",
|
||||
"private": true,
|
||||
"repository": {
|
||||
@@ -15,15 +15,15 @@
|
||||
},
|
||||
"author": "Grafana Labs",
|
||||
"devDependencies": {
|
||||
"@grafana/toolkit": "9.0.5",
|
||||
"@grafana/toolkit": "9.0.6",
|
||||
"@types/jest": "26.0.15",
|
||||
"@types/lodash": "4.14.149",
|
||||
"@types/react": "17.0.30",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grafana/data": "9.0.5",
|
||||
"@grafana/ui": "9.0.5",
|
||||
"@grafana/data": "9.0.6",
|
||||
"@grafana/ui": "9.0.6",
|
||||
"jquery": "3.5.1",
|
||||
"react": "17.0.1",
|
||||
"react-dom": "17.0.1",
|
||||
|
||||
@@ -6898,6 +6898,9 @@
|
||||
"200": {
|
||||
"$ref": "#/responses/snapshotResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/badRequestError"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFoundError"
|
||||
},
|
||||
|
||||
@@ -6898,6 +6898,9 @@
|
||||
"200": {
|
||||
"$ref": "#/responses/snapshotResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/badRequestError"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFoundError"
|
||||
},
|
||||
|
||||
@@ -131,7 +131,7 @@ export const RolePicker = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-testid="role-picker" style={{ position: 'relative' }} ref={ref}>
|
||||
<div data-testid="role-picker" style={{ position: 'relative', width: ROLE_PICKER_WIDTH }} ref={ref}>
|
||||
<ClickOutsideWrapper onClick={onClickOutside}>
|
||||
<RolePickerInput
|
||||
builtInRole={selectedBuiltInRole}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { css, cx } from '@emotion/css';
|
||||
import React, { useRef, useState, useLayoutEffect } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { sanitize, sanitizeUrl } from '@grafana/data/src/text/sanitize';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { Icon, ToolbarButton, Tooltip, useStyles2 } from '@grafana/ui';
|
||||
@@ -14,7 +15,7 @@ import { DashboardLink } from '../../state/DashboardModel';
|
||||
interface Props {
|
||||
link: DashboardLink;
|
||||
linkInfo: { title: string; href: string };
|
||||
dashboardId: any;
|
||||
dashboardId: number;
|
||||
}
|
||||
|
||||
export const DashboardLinksDashboard: React.FC<Props> = (props) => {
|
||||
@@ -23,13 +24,7 @@ export const DashboardLinksDashboard: React.FC<Props> = (props) => {
|
||||
const [dropdownCssClass, setDropdownCssClass] = useState('invisible');
|
||||
const [opened, setOpened] = useState(0);
|
||||
const resolvedLinks = useResolvedLinks(props, opened);
|
||||
|
||||
const buttonStyle = useStyles2(
|
||||
(theme) =>
|
||||
css`
|
||||
color: ${theme.colors.text.primary};
|
||||
`
|
||||
);
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setDropdownCssClass(getDropdownLocationCssClass(listRef.current));
|
||||
@@ -41,17 +36,22 @@ export const DashboardLinksDashboard: React.FC<Props> = (props) => {
|
||||
<>
|
||||
<ToolbarButton
|
||||
onClick={() => setOpened(Date.now())}
|
||||
className={cx('gf-form-label gf-form-label--dashlink', buttonStyle)}
|
||||
className={cx('gf-form-label gf-form-label--dashlink', styles.button)}
|
||||
data-placement="bottom"
|
||||
data-toggle="dropdown"
|
||||
aria-expanded={!!opened}
|
||||
aria-controls="dropdown-list"
|
||||
aria-haspopup="menu"
|
||||
>
|
||||
<Icon aria-hidden name="bars" style={{ marginRight: '4px' }} />
|
||||
<Icon aria-hidden name="bars" className={styles.iconMargin} />
|
||||
<span>{linkInfo.title}</span>
|
||||
</ToolbarButton>
|
||||
<ul id="dropdown-list" className={`dropdown-menu ${dropdownCssClass}`} role="menu" ref={listRef}>
|
||||
<ul
|
||||
id="dropdown-list"
|
||||
className={`dropdown-menu ${styles.dropdown} ${dropdownCssClass}`}
|
||||
role="menu"
|
||||
ref={listRef}
|
||||
>
|
||||
{resolvedLinks.length > 0 &&
|
||||
resolvedLinks.map((resolvedLink, index) => {
|
||||
return (
|
||||
@@ -130,13 +130,13 @@ const useResolvedLinks = ({ link, dashboardId }: Props, opened: number): Resolve
|
||||
};
|
||||
|
||||
interface ResolvedLinkDTO {
|
||||
id: any;
|
||||
id: number;
|
||||
url: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export async function searchForTags(
|
||||
tags: any[],
|
||||
tags: string[],
|
||||
dependencies: { getBackendSrv: typeof getBackendSrv } = { getBackendSrv }
|
||||
): Promise<DashboardSearchHit[]> {
|
||||
const limit = 100;
|
||||
@@ -146,7 +146,7 @@ export async function searchForTags(
|
||||
}
|
||||
|
||||
export function resolveLinks(
|
||||
dashboardId: any,
|
||||
dashboardId: number,
|
||||
link: DashboardLink,
|
||||
searchHits: DashboardSearchHit[],
|
||||
dependencies: { getLinkSrv: typeof getLinkSrv; sanitize: typeof sanitize; sanitizeUrl: typeof sanitizeUrl } = {
|
||||
@@ -185,3 +185,24 @@ function getDropdownLocationCssClass(element: HTMLElement | null) {
|
||||
return 'pull-right';
|
||||
}
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
iconMargin: css({
|
||||
marginRight: theme.spacing(0.5),
|
||||
}),
|
||||
dropdown: css({
|
||||
maxWidth: 'max(30vw, 300px)',
|
||||
maxHeight: '70vh',
|
||||
overflowY: 'auto',
|
||||
a: {
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
},
|
||||
}),
|
||||
button: css({
|
||||
color: theme.colors.text.primary,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,7 +28,16 @@ export function pointFieldFromGeohash(geohash: Field<string>): Field<Point> {
|
||||
export function pointFieldFromLonLat(lon: Field, lat: Field): Field<Point> {
|
||||
const buffer = new Array<Point>(lon.values.length);
|
||||
for (let i = 0; i < lon.values.length; i++) {
|
||||
buffer[i] = new Point(fromLonLat([lon.values.get(i), lat.values.get(i)]));
|
||||
const longitude = lon.values.get(i);
|
||||
const latitude = lat.values.get(i);
|
||||
|
||||
// TODO: Add unit tests to thoroughly test out edge cases
|
||||
// If longitude or latitude are null, don't add them to buffer
|
||||
if (longitude === null || latitude === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
buffer[i] = new Point(fromLonLat([longitude, latitude]));
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -19,7 +19,7 @@ import { defaultQuery, defaultQueryParams, queryReducer } from '../reducers/sear
|
||||
import { DashboardQuery, SearchLayout } from '../types';
|
||||
import { hasFilters, parseRouteParams } from '../utils';
|
||||
|
||||
const updateLocation = debounce((query) => locationService.partial(query), 300);
|
||||
const updateLocation = debounce((query) => locationService.partial(query, true), 300);
|
||||
|
||||
export const useSearchQuery = (defaults: Partial<DashboardQuery>) => {
|
||||
const queryParams = parseRouteParams(locationService.getSearchObject());
|
||||
|
||||
+3
@@ -122,6 +122,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
onChange={(value: string) => onNamespaceChange(value)}
|
||||
label="Namespace"
|
||||
inputId={`variable-query-namespace-${query.refId}`}
|
||||
allowCustomValue
|
||||
/>
|
||||
)}
|
||||
{parsedQuery.queryType === VariableQueryType.DimensionValues && (
|
||||
@@ -132,6 +133,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
onChange={(value: string) => onQueryChange({ ...parsedQuery, metricName: value })}
|
||||
label="Metric"
|
||||
inputId={`variable-query-metric-${query.refId}`}
|
||||
allowCustomValue
|
||||
/>
|
||||
<VariableQueryField
|
||||
value={dimensionKey || null}
|
||||
@@ -139,6 +141,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
onChange={(value: string) => onQueryChange({ ...parsedQuery, dimensionKey: value })}
|
||||
label="Dimension key"
|
||||
inputId={`variable-query-dimension-key-${query.refId}`}
|
||||
allowCustomValue
|
||||
/>
|
||||
<InlineField label="Dimensions" labelWidth={20} tooltip="Dimensions to filter the returned values on">
|
||||
<Dimensions
|
||||
|
||||
@@ -113,7 +113,7 @@ describe('graphiteDatasource', () => {
|
||||
const query = {
|
||||
panelId: 3,
|
||||
dashboardId: 5,
|
||||
range: { raw: { from: 'now-1h', to: 'now' } },
|
||||
range: { from: dateTime('2022-04-01T00:00:00'), to: dateTime('2022-07-01T00:00:00') },
|
||||
targets: [{ target: 'prod1.count' }, { target: 'prod2.count' }],
|
||||
maxDataPoints: 500,
|
||||
};
|
||||
@@ -157,8 +157,8 @@ describe('graphiteDatasource', () => {
|
||||
const params = requestOptions.data.split('&');
|
||||
expect(params).toContain('target=prod1.count');
|
||||
expect(params).toContain('target=prod2.count');
|
||||
expect(params).toContain('from=-1h');
|
||||
expect(params).toContain('until=now');
|
||||
expect(params).toContain('from=1648789200');
|
||||
expect(params).toContain('until=1656655200');
|
||||
});
|
||||
|
||||
it('should exclude undefined params', () => {
|
||||
|
||||
@@ -179,8 +179,8 @@ export class GraphiteDatasource
|
||||
|
||||
query(options: DataQueryRequest<GraphiteQuery>): Observable<DataQueryResponse> {
|
||||
const graphOptions = {
|
||||
from: this.translateTime(options.range.raw.from, false, options.timezone),
|
||||
until: this.translateTime(options.range.raw.to, true, options.timezone),
|
||||
from: this.translateTime(options.range.from, false, options.timezone),
|
||||
until: this.translateTime(options.range.to, true, options.timezone),
|
||||
targets: options.targets,
|
||||
format: (options as any).format,
|
||||
cacheTimeout: options.cacheTimeout || this.cacheTimeout,
|
||||
|
||||
@@ -18,7 +18,6 @@ import { PrometheusDatasource } from './datasource';
|
||||
import {
|
||||
addLimitInfo,
|
||||
extractLabelMatchers,
|
||||
fixSummariesMetadata,
|
||||
parseSelector,
|
||||
processHistogramMetrics,
|
||||
processLabels,
|
||||
@@ -146,9 +145,19 @@ export default class PromQlLanguageProvider extends LanguageProvider {
|
||||
};
|
||||
|
||||
async loadMetricsMetadata() {
|
||||
this.metricsMetadata = fixSummariesMetadata(
|
||||
await this.request('/api/v1/metadata', {}, {}, { showErrorAlert: false })
|
||||
);
|
||||
// The metadata endpoint is experimental and
|
||||
// we have customers who are not implementing it.
|
||||
// This is to be a temporary fix until the Observability Metrics Squad
|
||||
// has time to implement the 2022Q3 plan
|
||||
// to detect prometheus versions and implementations.
|
||||
// This will allow us to see if endpoints such as api/v1/metadata
|
||||
// are implemented or not and handle them as such
|
||||
this.metricsMetadata = {};
|
||||
|
||||
// PREVIOUS IMPLEMENTATION FOR NOTES
|
||||
// this.metricsMetadata = fixSummariesMetadata(
|
||||
// await this.request('/api/v1/metadata', {}, {}, { showErrorAlert: false })
|
||||
// );
|
||||
}
|
||||
|
||||
getLabelKeys(): string[] {
|
||||
|
||||
@@ -96,6 +96,28 @@ export class GeomapPanel extends Component<Props, State> {
|
||||
|
||||
componentDidMount() {
|
||||
this.panelContext = this.context as PanelContext;
|
||||
// TODO: Clean this approach up / potentially support multiple marker layers?
|
||||
// See https://github.com/grafana/grafana/issues/51185 for more details.
|
||||
setTimeout(() => {
|
||||
for (const layer of this.layers) {
|
||||
if (layer.options.type === MARKERS_LAYER_ID) {
|
||||
const colorField = layer.options.config.style.color.field;
|
||||
const colorFieldData = this.props.data.series[0].fields.find((field) => field.name === colorField);
|
||||
// initialize (not override) Standard Options min/max value with color field calc min/max
|
||||
if (colorFieldData) {
|
||||
this.props.onFieldConfigChange({
|
||||
...this.props.fieldConfig,
|
||||
defaults: {
|
||||
min: colorFieldData.state?.calcs?.min,
|
||||
max: colorFieldData.state?.calcs?.max,
|
||||
...this.props.fieldConfig.defaults,
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps: Props) {
|
||||
@@ -244,6 +266,31 @@ export class GeomapPanel extends Component<Props, State> {
|
||||
console.log('Controls changed');
|
||||
this.initControls(options.controls ?? { showZoom: true, showAttribution: true });
|
||||
}
|
||||
|
||||
// TODO: Clean this approach up / potentially support multiple marker layers?
|
||||
// See https://github.com/grafana/grafana/issues/51185 for more details.
|
||||
for (const layer of options.layers) {
|
||||
if (layer.type === MARKERS_LAYER_ID) {
|
||||
const oldLayer = this.props.options.layers.find((lyr) => lyr.name === layer.name);
|
||||
const newLayerColorField = layer.config.style.color.field;
|
||||
const oldLayerColorField = oldLayer?.config.style.color.field;
|
||||
if (layer.config.style.color.field && newLayerColorField !== oldLayerColorField) {
|
||||
const colorFieldData = this.props.data.series[0].fields.find((field) => field.name === newLayerColorField);
|
||||
if (colorFieldData) {
|
||||
// override Standard Options min/max value with color field calc min/max
|
||||
this.props.onFieldConfigChange({
|
||||
...this.props.fieldConfig,
|
||||
defaults: {
|
||||
...this.props.fieldConfig.defaults,
|
||||
min: colorFieldData.state?.calcs?.min,
|
||||
max: colorFieldData.state?.calcs?.max,
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -355,8 +402,8 @@ export class GeomapPanel extends Component<Props, State> {
|
||||
const hover = toLonLat(this.map.getCoordinateFromPixel(pixel));
|
||||
|
||||
const { hoverPayload } = this;
|
||||
hoverPayload.pageX = mouse.pageX;
|
||||
hoverPayload.pageY = mouse.pageY;
|
||||
hoverPayload.pageX = mouse.offsetX;
|
||||
hoverPayload.pageY = mouse.offsetY;
|
||||
hoverPayload.point = {
|
||||
lat: hover[1],
|
||||
lon: hover[0],
|
||||
|
||||
@@ -48,6 +48,7 @@ export interface HistogramProps extends Themeable2 {
|
||||
height: number;
|
||||
structureRev?: number; // a number that will change when the frames[] structure changes
|
||||
legend: VizLegendOptions;
|
||||
rawSeries?: DataFrame[];
|
||||
children?: (builder: UPlotConfigBuilder, frame: DataFrame) => React.ReactNode;
|
||||
}
|
||||
|
||||
@@ -211,8 +212,8 @@ const prepConfig = (frame: DataFrame, theme: GrafanaTheme2) => {
|
||||
|
||||
// The following properties are not used in the uPlot config, but are utilized as transport for legend config
|
||||
dataFrameFieldIndex: {
|
||||
fieldIndex: i,
|
||||
frameIndex: 0,
|
||||
fieldIndex: 1,
|
||||
frameIndex: i - 2,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -273,11 +274,11 @@ export class Histogram extends React.Component<HistogramProps, State> {
|
||||
|
||||
renderLegend(config: UPlotConfigBuilder) {
|
||||
const { legend } = this.props;
|
||||
if (!config || legend.displayMode === LegendDisplayMode.Hidden) {
|
||||
if (!config || legend.displayMode === LegendDisplayMode.Hidden || !this.props.rawSeries) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <PlotLegend data={[this.props.alignedFrame]} config={config} maxHeight="35%" maxWidth="60%" {...legend} />;
|
||||
return <PlotLegend data={this.props.rawSeries} config={config} maxHeight="35%" maxWidth="60%" {...legend} />;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: HistogramProps) {
|
||||
|
||||
@@ -45,6 +45,7 @@ export const HistogramPanel: React.FC<Props> = ({ data, options, width, height }
|
||||
options={options}
|
||||
theme={theme}
|
||||
legend={options.legend}
|
||||
rawSeries={data.series}
|
||||
structureRev={data.structureRev}
|
||||
width={width}
|
||||
height={height}
|
||||
|
||||
@@ -61,6 +61,7 @@ export const TimeSeriesPanel: React.FC<TimeSeriesPanelProps> = ({
|
||||
width={width}
|
||||
height={height}
|
||||
legend={options.legend}
|
||||
options={options}
|
||||
>
|
||||
{(config, alignedDataFrame) => {
|
||||
return (
|
||||
|
||||
@@ -3791,9 +3791,9 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana-plugins/input-datasource@workspace:plugins-bundled/internal/input-datasource"
|
||||
dependencies:
|
||||
"@grafana/data": 9.0.5
|
||||
"@grafana/toolkit": 9.0.5
|
||||
"@grafana/ui": 9.0.5
|
||||
"@grafana/data": 9.0.6
|
||||
"@grafana/toolkit": 9.0.6
|
||||
"@grafana/ui": 9.0.6
|
||||
"@types/jest": 26.0.15
|
||||
"@types/lodash": 4.14.149
|
||||
"@types/react": 17.0.30
|
||||
@@ -3831,12 +3831,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/data@9.0.5, @grafana/data@workspace:*, @grafana/data@workspace:packages/grafana-data":
|
||||
"@grafana/data@9.0.6, @grafana/data@workspace:*, @grafana/data@workspace:packages/grafana-data":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana/data@workspace:packages/grafana-data"
|
||||
dependencies:
|
||||
"@braintree/sanitize-url": 6.0.0
|
||||
"@grafana/schema": 9.0.5
|
||||
"@grafana/schema": 9.0.6
|
||||
"@grafana/tsconfig": ^1.2.0-rc1
|
||||
"@rollup/plugin-commonjs": 22.0.0
|
||||
"@rollup/plugin-json": 4.1.0
|
||||
@@ -3889,7 +3889,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana/e2e-selectors@9.0.5, @grafana/e2e-selectors@workspace:*, @grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors":
|
||||
"@grafana/e2e-selectors@9.0.6, @grafana/e2e-selectors@workspace:*, @grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors"
|
||||
dependencies:
|
||||
@@ -3913,7 +3913,7 @@ __metadata:
|
||||
"@babel/core": 7.17.8
|
||||
"@babel/preset-env": 7.17.10
|
||||
"@cypress/webpack-preprocessor": 5.11.1
|
||||
"@grafana/e2e-selectors": 9.0.5
|
||||
"@grafana/e2e-selectors": 9.0.6
|
||||
"@grafana/tsconfig": ^1.2.0-rc1
|
||||
"@mochajs/json-file-reporter": ^1.2.0
|
||||
"@rollup/plugin-commonjs": 22.0.0
|
||||
@@ -3998,14 +3998,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/runtime@9.0.5, @grafana/runtime@workspace:*, @grafana/runtime@workspace:packages/grafana-runtime":
|
||||
"@grafana/runtime@9.0.6, @grafana/runtime@workspace:*, @grafana/runtime@workspace:packages/grafana-runtime":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana/runtime@workspace:packages/grafana-runtime"
|
||||
dependencies:
|
||||
"@grafana/data": 9.0.5
|
||||
"@grafana/e2e-selectors": 9.0.5
|
||||
"@grafana/data": 9.0.6
|
||||
"@grafana/e2e-selectors": 9.0.6
|
||||
"@grafana/tsconfig": ^1.2.0-rc1
|
||||
"@grafana/ui": 9.0.5
|
||||
"@grafana/ui": 9.0.6
|
||||
"@rollup/plugin-commonjs": 22.0.0
|
||||
"@rollup/plugin-node-resolve": 13.3.0
|
||||
"@sentry/browser": 6.19.7
|
||||
@@ -4034,7 +4034,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana/schema@9.0.5, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema":
|
||||
"@grafana/schema@9.0.6, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana/schema@workspace:packages/grafana-schema"
|
||||
dependencies:
|
||||
@@ -4081,7 +4081,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/toolkit@9.0.5, @grafana/toolkit@workspace:*, @grafana/toolkit@workspace:packages/grafana-toolkit":
|
||||
"@grafana/toolkit@9.0.6, @grafana/toolkit@workspace:*, @grafana/toolkit@workspace:packages/grafana-toolkit":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana/toolkit@workspace:packages/grafana-toolkit"
|
||||
dependencies:
|
||||
@@ -4097,10 +4097,10 @@ __metadata:
|
||||
"@babel/preset-env": ^7.16.11
|
||||
"@babel/preset-react": ^7.16.7
|
||||
"@babel/preset-typescript": ^7.16.7
|
||||
"@grafana/data": 9.0.5
|
||||
"@grafana/data": 9.0.6
|
||||
"@grafana/eslint-config": ^4.0.0
|
||||
"@grafana/tsconfig": ^1.2.0-rc1
|
||||
"@grafana/ui": 9.0.5
|
||||
"@grafana/ui": 9.0.6
|
||||
"@jest/core": 27.5.1
|
||||
"@types/command-exists": ^1.2.0
|
||||
"@types/eslint": 8.4.1
|
||||
@@ -4184,7 +4184,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/ui@9.0.5, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui":
|
||||
"@grafana/ui@9.0.6, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana/ui@workspace:packages/grafana-ui"
|
||||
dependencies:
|
||||
@@ -4192,9 +4192,9 @@ __metadata:
|
||||
"@emotion/css": 11.9.0
|
||||
"@emotion/react": 11.9.0
|
||||
"@grafana/aws-sdk": 0.0.36
|
||||
"@grafana/data": 9.0.5
|
||||
"@grafana/e2e-selectors": 9.0.5
|
||||
"@grafana/schema": 9.0.5
|
||||
"@grafana/data": 9.0.6
|
||||
"@grafana/e2e-selectors": 9.0.6
|
||||
"@grafana/schema": 9.0.6
|
||||
"@grafana/slate-react": 0.22.10-grafana
|
||||
"@grafana/tsconfig": ^1.2.0-rc1
|
||||
"@mdx-js/react": 1.6.22
|
||||
@@ -4431,11 +4431,11 @@ __metadata:
|
||||
resolution: "@jaegertracing/jaeger-ui-components@workspace:packages/jaeger-ui-components"
|
||||
dependencies:
|
||||
"@emotion/css": 11.9.0
|
||||
"@grafana/data": 9.0.5
|
||||
"@grafana/e2e-selectors": 9.0.5
|
||||
"@grafana/runtime": 9.0.5
|
||||
"@grafana/data": 9.0.6
|
||||
"@grafana/e2e-selectors": 9.0.6
|
||||
"@grafana/runtime": 9.0.6
|
||||
"@grafana/tsconfig": ^1.2.0-rc1
|
||||
"@grafana/ui": 9.0.5
|
||||
"@grafana/ui": 9.0.6
|
||||
"@testing-library/react": 12.1.4
|
||||
"@testing-library/user-event": 14.2.0
|
||||
"@types/classnames": ^2.2.7
|
||||
|
||||
Reference in New Issue
Block a user