Merge branch 'v9.0.x' of https://github.com/grafana/grafana into v9.0.x

This commit is contained in:
ismail simsek
2022-08-10 10:52:08 +02:00
54 changed files with 429 additions and 173 deletions
+16 -11
View File
@@ -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"],
+15
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+5 -11
View File
@@ -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.
+1
View File
@@ -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
+3 -1
View File
@@ -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.
@@ -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
View File
@@ -4,5 +4,5 @@
"packages": [
"packages/*"
],
"version": "9.0.5"
"version": "9.0.6"
}
+1 -1
View File
@@ -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 -2
View File
@@ -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",
+1 -1
View File
@@ -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 -2
View File
@@ -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",
+4 -4
View File
@@ -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",
+1 -1
View File
@@ -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"
+3 -3
View File
@@ -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/',
},
},
+4 -4
View File
@@ -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>
+5 -5
View File
@@ -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",
+13 -4
View File
@@ -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)
+69
View File
@@ -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)
}
+1
View File
@@ -30,6 +30,7 @@ import (
//
// Responses:
// 200: snapshotResponse
// 400: badRequestError
// 404: notFoundError
// 500: internalServerError
+7 -2
View File
@@ -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 {
+5 -4
View File
@@ -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)
+7 -5
View File
@@ -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) {
+2
View File
@@ -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",
+3
View File
@@ -6898,6 +6898,9 @@
"200": {
"$ref": "#/responses/snapshotResponse"
},
"400": {
"$ref": "#/responses/badRequestError"
},
"404": {
"$ref": "#/responses/notFoundError"
},
+3
View File
@@ -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,
}),
};
}
+10 -1
View File
@@ -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());
@@ -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 (
+23 -23
View File
@@ -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