mirror of
https://github.com/grafana/grafana.git
synced 2026-01-07 22:41:10 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9cb2a313e |
@@ -1,30 +0,0 @@
|
||||
name: "publish-technical-documentation-next"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- "docs/sources/**"
|
||||
- "packages/grafana-*/**"
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- name: "Checkout Grafana repo"
|
||||
uses: "actions/checkout@v3"
|
||||
|
||||
- name: "Clone website-sync Action"
|
||||
run: "git clone --single-branch --no-tags --depth 1 -b master https://grafanabot:${{ secrets.GH_BOT_ACCESS_TOKEN }}@github.com/grafana/website-sync ./.github/actions/website-sync"
|
||||
|
||||
- name: "Publish to website repository (next)"
|
||||
uses: "./.github/actions/website-sync"
|
||||
id: "publish-next"
|
||||
with:
|
||||
repository: "grafana/website"
|
||||
branch: "master"
|
||||
host: "github.com"
|
||||
github_pat: "${{ secrets.GH_BOT_ACCESS_TOKEN }}"
|
||||
source_folder: "docs/sources"
|
||||
target_folder: "content/docs/grafana/next"
|
||||
@@ -1,60 +0,0 @@
|
||||
name: "publish-technical-documentation-release"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- v[0-9]+.[0-9]+.[0-9]+
|
||||
tags:
|
||||
- v[0-9]+.[0-9]+.[0-9]+
|
||||
paths:
|
||||
- "docs/sources/**"
|
||||
- "packages/grafana-*/**"
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- name: "Checkout Grafana repo"
|
||||
uses: "actions/checkout@v3"
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: "Checkout Actions library"
|
||||
uses: "actions/checkout@v3"
|
||||
with:
|
||||
repository: "grafana/grafana-github-actions"
|
||||
path: "./actions"
|
||||
|
||||
- name: "Install Actions from library"
|
||||
run: "npm install --production --prefix ./actions"
|
||||
|
||||
- name: "Determine if there is a matching release tag"
|
||||
id: "has-matching-release-tag"
|
||||
uses: "./actions/has-matching-release-tag"
|
||||
with:
|
||||
ref_name: "${{ github.ref_name }}"
|
||||
release_tag_regexp: "^v(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$"
|
||||
release_branch_regexp: "^v(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.x$"
|
||||
|
||||
- name: "Determine technical documentation version"
|
||||
if: "steps.has-matching-release-tag.outputs.bool == 'true'"
|
||||
uses: "./actions/docs-target"
|
||||
id: "target"
|
||||
with:
|
||||
ref_name: "${{ github.ref_name }}"
|
||||
|
||||
- name: "Clone website-sync Action"
|
||||
if: "steps.has-matching-release-tag.outputs.bool == 'true'"
|
||||
run: "git clone --single-branch --no-tags --depth 1 -b master https://grafanabot:${{ secrets.GH_BOT_ACCESS_TOKEN }}@github.com/grafana/website-sync ./.github/actions/website-sync"
|
||||
|
||||
- name: "Publish to website repository (release)"
|
||||
if: "steps.has-matching-release-tag.outputs.bool == 'true'"
|
||||
uses: "./.github/actions/website-sync"
|
||||
id: "publish-release"
|
||||
with:
|
||||
repository: "grafana/website"
|
||||
branch: "master"
|
||||
host: "github.com"
|
||||
github_pat: "${{ secrets.GH_BOT_ACCESS_TOKEN }}"
|
||||
source_folder: "docs/sources"
|
||||
target_folder: "content/docs/grafana/${{ steps.target.outputs.target }}"
|
||||
47
.github/workflows/publish.yml
vendored
Normal file
47
.github/workflows/publish.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: publish_docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'docs/sources/**'
|
||||
- 'packages/grafana-*/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: git clone --single-branch --no-tags --depth 1 -b master https://grafanabot:${{ secrets.GH_BOT_ACCESS_TOKEN }}@github.com/grafana/website-sync ./.github/actions/website-sync
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v3.5.1
|
||||
with:
|
||||
node-version: '16'
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
- uses: actions/cache@v3.0.11
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
yarn-
|
||||
- run: yarn install --immutable
|
||||
- name: publish-to-git
|
||||
uses: ./.github/actions/website-sync
|
||||
id: publish
|
||||
with:
|
||||
repository: grafana/website
|
||||
branch: master
|
||||
host: github.com
|
||||
github_pat: '${{ secrets.GH_BOT_ACCESS_TOKEN }}'
|
||||
source_folder: docs/sources
|
||||
target_folder: content/docs/grafana/next
|
||||
allow_no_changes: 'true'
|
||||
- shell: bash
|
||||
run: |
|
||||
test -n "${{ steps.publish.outputs.commit_hash }}"
|
||||
test -n "${{ steps.publish.outputs.working_directory }}"
|
||||
65
CHANGELOG.md
65
CHANGELOG.md
@@ -1,68 +1,3 @@
|
||||
<!-- 9.3.0 START -->
|
||||
|
||||
# 9.3.0 (2022-11-30)
|
||||
|
||||
### Features and enhancements
|
||||
|
||||
- **Alerting:** Enable interpolation for notification policies in file provisioning. [#58956](https://github.com/grafana/grafana/pull/58956), [@JohnnyQQQQ](https://github.com/JohnnyQQQQ)
|
||||
- **Azure Monitor Logs:** Avoid warning when the response is empty. [#59211](https://github.com/grafana/grafana/pull/59211), [@andresmgot](https://github.com/andresmgot)
|
||||
- **Azure Monitor:** Add support to customized routes. [#54829](https://github.com/grafana/grafana/pull/54829), [@ms-hujia](https://github.com/ms-hujia)
|
||||
- **Canvas:** Add icon value mapping. [#59013](https://github.com/grafana/grafana/pull/59013), [@nmarrs](https://github.com/nmarrs)
|
||||
- **CloudWatch:** Cross-account querying support. [#59362](https://github.com/grafana/grafana/pull/59362), [@sunker](https://github.com/sunker)
|
||||
- **Docs:** Update `merge-pull-request.md` regarding backport policies. [#59239](https://github.com/grafana/grafana/pull/59239), [@dsotirakis](https://github.com/dsotirakis)
|
||||
- **GaugePanel:** Setting the neutral-point of a gauge. [#53989](https://github.com/grafana/grafana/pull/53989), [@sfranzis](https://github.com/sfranzis)
|
||||
- **Geomap:** Improve location editor. [#58017](https://github.com/grafana/grafana/pull/58017), [@drew08t](https://github.com/drew08t)
|
||||
- **Internationalization:** Enable internationalization by default. [#59204](https://github.com/grafana/grafana/pull/59204), [@joshhunt](https://github.com/joshhunt)
|
||||
- **Logs:** Add `Download logs` button to log log-browser. [#55163](https://github.com/grafana/grafana/pull/55163), [@svennergr](https://github.com/svennergr)
|
||||
- **Loki:** Add `gzip` compression to resource calls. [#59059](https://github.com/grafana/grafana/pull/59059), [@svennergr](https://github.com/svennergr)
|
||||
- **Loki:** Add improvements to loki label browser. [#59387](https://github.com/grafana/grafana/pull/59387), [@gwdawson](https://github.com/gwdawson)
|
||||
- **Loki:** Make label browser accessible in query builder. [#58525](https://github.com/grafana/grafana/pull/58525), [@gwdawson](https://github.com/gwdawson)
|
||||
- **Loki:** Remove raw query toggle. [#59125](https://github.com/grafana/grafana/pull/59125), [@gwdawson](https://github.com/gwdawson)
|
||||
- **Middleware:** Add CSP Report Only support. [#58074](https://github.com/grafana/grafana/pull/58074), [@jcalisto](https://github.com/jcalisto)
|
||||
- **Navigation:** Prevent viewer role accessing dashboard creation, import and folder creation. [#58842](https://github.com/grafana/grafana/pull/58842), [@lpskdl](https://github.com/lpskdl)
|
||||
- **OAuth:** Refactor OAuth parameters handling to support obtaining refresh tokens for Google OAuth. [#58782](https://github.com/grafana/grafana/pull/58782), [@mgyongyosi](https://github.com/mgyongyosi)
|
||||
- **Oauth:** Display friendly error message when role_attribute_strict=true and no valid role found. [#57818](https://github.com/grafana/grafana/pull/57818), [@kalleep](https://github.com/kalleep)
|
||||
- **Preferences:** Add confirmation modal when saving org preferences. [#59119](https://github.com/grafana/grafana/pull/59119), [@JoaoSilvaGrafana](https://github.com/JoaoSilvaGrafana)
|
||||
- **PublicDashboards:** Orphaned public dashboard deletion script added. [#57917](https://github.com/grafana/grafana/pull/57917), [@juanicabanas](https://github.com/juanicabanas)
|
||||
- **Query Editor:** Hide overflow for long query names. [#58840](https://github.com/grafana/grafana/pull/58840), [@zuchka](https://github.com/zuchka)
|
||||
- **Reports:** Configurable timezone. (Enterprise)
|
||||
- **Solo Panel:** Configurable timezone. [#59153](https://github.com/grafana/grafana/pull/59153), [@spinillos](https://github.com/spinillos)
|
||||
- **TablePanel:** Add support for Count calculation per column or per entire dataset. [#58134](https://github.com/grafana/grafana/pull/58134), [@mdvictor](https://github.com/mdvictor)
|
||||
- **Tempo:** Send the correct start time when making a TraceQL query. [#59128](https://github.com/grafana/grafana/pull/59128), [@CrypticSignal](https://github.com/CrypticSignal)
|
||||
- **Various Panels:** Remove beta label from Bar Chart, Candlestick, Histogram, State Timeline, & Status History Panels. [#58557](https://github.com/grafana/grafana/pull/58557), [@codeincarnate](https://github.com/codeincarnate)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- **Access Control:** Clear user's permission cache after resource creation. [#59307](https://github.com/grafana/grafana/pull/59307), [@grafanabot](https://github.com/grafanabot)
|
||||
- **Access Control:** Clear user's permission cache after resource creation. [#59101](https://github.com/grafana/grafana/pull/59101), [@IevaVasiljeva](https://github.com/IevaVasiljeva)
|
||||
- **Accessibility:** Improve keyboard accessibility in `AnnoListPanel`. [#58971](https://github.com/grafana/grafana/pull/58971), [@ashharrison90](https://github.com/ashharrison90)
|
||||
- **Accessibility:** Improve keyboard accessibility in `Collapse`. [#59022](https://github.com/grafana/grafana/pull/59022), [@ashharrison90](https://github.com/ashharrison90)
|
||||
- **Accessibility:** Improve keyboard accessibility in `GettingStarted` panel. [#58966](https://github.com/grafana/grafana/pull/58966), [@ashharrison90](https://github.com/ashharrison90)
|
||||
- **Accessibility:** Improve keyboard accessibility of `FilterPill`. [#58976](https://github.com/grafana/grafana/pull/58976), [@ashharrison90](https://github.com/ashharrison90)
|
||||
- **Admin:** Fix broken links to image assets in email templates. [#58729](https://github.com/grafana/grafana/pull/58729), [@zuchka](https://github.com/zuchka)
|
||||
- **Azure Monitor:** Fix namespace selection for storageaccounts. [#56449](https://github.com/grafana/grafana/pull/56449), [@andresmgot](https://github.com/andresmgot)
|
||||
- **Calcs:** Fix difference percent in legend. [#59243](https://github.com/grafana/grafana/pull/59243), [@zoltanbedi](https://github.com/zoltanbedi)
|
||||
- **DataLinks:** Improve Data-Links AutoComplete Logic. [#58934](https://github.com/grafana/grafana/pull/58934), [@zuchka](https://github.com/zuchka)
|
||||
- **Explore:** Fix a11y issue with logs navigation buttons. [#58944](https://github.com/grafana/grafana/pull/58944), [@Elfo404](https://github.com/Elfo404)
|
||||
- **Heatmap:** Fix blurry text & rendering. [#59260](https://github.com/grafana/grafana/pull/59260), [@leeoniya](https://github.com/leeoniya)
|
||||
- **Heatmap:** Fix tooltip y range of top and bottom buckets in calculated heatmaps. [#59172](https://github.com/grafana/grafana/pull/59172), [@leeoniya](https://github.com/leeoniya)
|
||||
- **Logs:** Fix misalignment of LogRows. [#59279](https://github.com/grafana/grafana/pull/59279), [@svennergr](https://github.com/svennergr)
|
||||
- **Navigation:** Stop clearing search state when opening a result in a new tab. [#58880](https://github.com/grafana/grafana/pull/58880), [@ashharrison90](https://github.com/ashharrison90)
|
||||
- **OptionsUI:** SliderValueEditor does not get auto focused on slider change. [#59209](https://github.com/grafana/grafana/pull/59209), [@eledobleefe](https://github.com/eledobleefe)
|
||||
- **PanelEdit:** Fixes bug with not remembering panel options pane collapse/expand state. [#59265](https://github.com/grafana/grafana/pull/59265), [@torkelo](https://github.com/torkelo)
|
||||
- **Query Caching:** Skip 207 status codes. (Enterprise)
|
||||
- **Quota:** Fix failure in store due to missing scope parameters. [#58874](https://github.com/grafana/grafana/pull/58874), [@papagian](https://github.com/papagian)
|
||||
- **Quota:** Fix failure when checking session limits. [#58865](https://github.com/grafana/grafana/pull/58865), [@papagian](https://github.com/papagian)
|
||||
- **Reports:** Fix time preview. (Enterprise)
|
||||
- **StateTimeline:** Prevent label text from overflowing state rects. [#59169](https://github.com/grafana/grafana/pull/59169), [@leeoniya](https://github.com/leeoniya)
|
||||
- **Tempo:** Fix search table duration unit. [#58642](https://github.com/grafana/grafana/pull/58642), [@joey-grafana](https://github.com/joey-grafana)
|
||||
- **TraceView:** Fix broken rendering when scrolling in Dashboard panel in Firefox. [#56642](https://github.com/grafana/grafana/pull/56642), [@zdg-github](https://github.com/zdg-github)
|
||||
|
||||
### Plugin development fixes & changes
|
||||
|
||||
- **GrafanaUI:** Add disabled option for menu items. [#58980](https://github.com/grafana/grafana/pull/58980), [@going-confetti](https://github.com/going-confetti)
|
||||
|
||||
<!-- 9.3.0 END -->
|
||||
<!-- 9.3.0-beta1 START -->
|
||||
|
||||
# 9.3.0-beta1 (2022-11-15)
|
||||
|
||||
@@ -101,7 +101,7 @@ Here is an example of the light theme.
|
||||
|
||||
### Change server UI theme
|
||||
|
||||
As a Grafana server administrator, you can change the default Grafana UI theme for all users who are on the server by setting the [default_theme]({{< relref "../../setup-grafana/configure-grafana/#default-theme" >}}) option in the Grafana configuration file.
|
||||
Grafana server administrators can change the Grafana UI theme for all users on the server by setting the [default_theme]({{< relref "../../setup-grafana/configure-grafana/#default-theme" >}}) option in the Grafana configuration file.
|
||||
|
||||
To see what the current settings are, refer to [View server settings]({{< relref "../stats-and-license#view-server-settings" >}}).
|
||||
|
||||
@@ -111,18 +111,17 @@ Organization administrators can change the UI theme for all users in an organiza
|
||||
|
||||
1. Hover your cursor over the **Configuration** (gear) icon.
|
||||
1. Click **Preferences**.
|
||||
1. In the **Preferences** section, select the **UI theme**.
|
||||
1. In the Preferences section, select the **UI theme**.
|
||||
1. Click **Save**.
|
||||
|
||||
### Change team UI theme
|
||||
|
||||
Organization and team administrators can change the UI theme for all users on a team.
|
||||
Organization and team administrators can change the UI theme for all users in a team.
|
||||
|
||||
1. Hover your cursor over the **Configuration** (gear) icon in the side menu.
|
||||
1. Click **Teams**. Grafana displays the team list.
|
||||
1. Click the team for which you want to change the UI theme.
|
||||
1. Click **Settings**.
|
||||
1. In the **Preferences** section, select the **UI theme**.
|
||||
1. Click on the team that you want to change the UI theme for and then navigate to the **Settings** tab.
|
||||
1. In the Preferences section, select the **UI theme**.
|
||||
1. Click **Save**.
|
||||
|
||||
### Change your personal UI theme
|
||||
@@ -130,7 +129,7 @@ Organization and team administrators can change the UI theme for all users on a
|
||||
You can change the UI theme for your user account. This setting overrides UI theme settings at higher levels.
|
||||
|
||||
1. On the left menu, hover your cursor over your avatar and then click **Preferences**.
|
||||
1. In the **Preferences** section, select the **UI theme**.
|
||||
1. In the Preferences section, select the **UI theme**.
|
||||
1. Click **Save**.
|
||||
|
||||
## Change the Grafana default timezone
|
||||
@@ -154,12 +153,11 @@ Organization administrators can choose a default timezone for their organization
|
||||
|
||||
### Set team timezone
|
||||
|
||||
Organization administrators and team administrators can choose a default timezone for all users on a team.
|
||||
Organization administrators and team administrators can choose a default timezone for all users in a team.
|
||||
|
||||
1. Hover your cursor over the **Configuration** (gear) icon in the side menu.
|
||||
1. Click **Teams**. Grafana displays the team list.
|
||||
1. Click the team for which you want to change the timezone.
|
||||
1. Click **Settings**
|
||||
1. Click on the team you that you want to change the timezone for and then navigate to the **Settings** tab.
|
||||
1. Click to select an option in the **Timezone** list. **Default** is either the browser local timezone or the timezone selected at a higher level. Refer to [[Time range controls]({{< relref "../../dashboards/manage-dashboards/#configure-dashboard-time-range-controls" >}}) for more information about Grafana time settings.
|
||||
1. Click **Save**.
|
||||
|
||||
@@ -209,7 +207,7 @@ default_home_dashboard_path = data/main-dashboard.json
|
||||
|
||||
### Set the home dashboard for your organization
|
||||
|
||||
Organization administrators can choose a default home dashboard for their organization.
|
||||
Organization administrators can choose a home dashboard for their organization.
|
||||
|
||||
1. Navigate to the dashboard you want to set as the home dashboard.
|
||||
1. Click the star next to the dashboard title to mark the dashboard as a favorite if it is not already.
|
||||
@@ -220,14 +218,13 @@ Organization administrators can choose a default home dashboard for their organi
|
||||
|
||||
### Set home dashboard for your team
|
||||
|
||||
Organization administrators and Team Admins can set a default home dashboard for all users on a team.
|
||||
Organization administrators and Team Admins can choose a home dashboard for a team.
|
||||
|
||||
1. Navigate to the dashboard you want to set as the home dashboard.
|
||||
1. Click the star next to the dashboard title to mark the dashboard as a favorite if it is not already.
|
||||
1. Hover your cursor over the **Configuration** (gear) icon in the side menu.
|
||||
1. Click **Teams**. Grafana displays the team list.
|
||||
1. Click the team for which you want to change the home dashboard.
|
||||
1. Click **Settings**.
|
||||
1. Click on the team that you want to change the home dashboard for and then navigate to the **Settings** tab.
|
||||
1. In the **Home Dashboard** field, select the dashboard that you want to use for your home dashboard. Options include all starred dashboards.
|
||||
1. Click **Save**.
|
||||
|
||||
@@ -240,37 +237,3 @@ You can choose your own personal home dashboard. This setting overrides all home
|
||||
1. On the left menu, hover your cursor over your avatar and then click **Preferences**.
|
||||
1. In the **Home Dashboard** field, select the dashboard that you want to use for your home dashboard. Options include all starred dashboards.
|
||||
1. Click **Save**.
|
||||
|
||||
## Change Grafana language
|
||||
|
||||
### Change server language
|
||||
|
||||
Grafana server administrators can change the default Grafana UI language for all users on the server by setting the [default_language]({{< relref "../../setup-grafana/configure-grafana/#default-language" >}}) option in the Grafana configuration file.
|
||||
|
||||
### Change organization language
|
||||
|
||||
Organization administrators can change the language for all users in an organization.
|
||||
|
||||
1. Hover your cursor over the **Configuration** (gear) icon.
|
||||
1. Click **Preferences**.
|
||||
1. In the **Preferences** section, select the **Language**.
|
||||
1. Click **Save**.
|
||||
|
||||
### Change team language
|
||||
|
||||
Organization and team administrators can set a default language for all users on a team.
|
||||
|
||||
1. Hover your cursor over the **Configuration** (gear) icon in the side menu.
|
||||
1. Click **Teams**. Grafana displays the team list.
|
||||
1. Click the team for which you want to change the language.
|
||||
1. Click **Settings**
|
||||
1. In the **Preferences** section, select the **Language**.
|
||||
1. Click **Save**.
|
||||
|
||||
### Change your personal language
|
||||
|
||||
You can change the language for your user account. This setting overrides language settings at higher levels.
|
||||
|
||||
1. On the left menu, hover your cursor over your avatar and then click **Preferences**.
|
||||
1. In the **Preferences** section, select the **language**.
|
||||
1. Click **Save**.
|
||||
|
||||
@@ -29,7 +29,9 @@ You can change your Grafana password at any time.
|
||||
|
||||
1. Sign in to Grafana.
|
||||
1. Hover your mouse over the user icon in the lower-left corner of the page.
|
||||
1. Click **Change Password**. Grafana opens the **Change Password** tab.
|
||||
1. Click **Change Password**.
|
||||
Grafana opens the **Change Password** tab.
|
||||
|
||||
1. Enter your old password and a new password.
|
||||
1. Confirm your new password.
|
||||
1. Click **Change Password**.
|
||||
@@ -52,7 +54,6 @@ You can choose the way you would like data to appear in Grafana, including the U
|
||||
- **Home dashboard** refers to the dashboard you see when you sign in to Grafana. By default, this is set to the Home dashboard.
|
||||
- **Timezone** is used by dashboards when you set time ranges, so that you view data in your timezone instead of UTC.
|
||||
- **Week start** is the first day of the week you want to use in dashboard time ranges, for example, `This week`.
|
||||
- **Language** determines the language used for parts of the Grafana interface.
|
||||
|
||||
**To edit your preferences**:
|
||||
|
||||
|
||||
@@ -87,17 +87,3 @@ The following template variables are available when expanding labels and annotat
|
||||
| $labels | The labels from the query or condition. For example, `{{ $labels.instance }}` and `{{ $labels.job }}`. This is unavailable when the rule uses a [classic condition]({{< relref "../../alerting-rules/create-grafana-managed-rule/#single-and-multi-dimensional-rule" >}}). |
|
||||
| $values | The values of all reduce and math expressions that were evaluated for this alert rule. For example, `{{ $values.A }}`, `{{ $values.A.Labels }}` and `{{ $values.A.Value }}` where `A` is the `refID` of the reduce or math expression. If the rule uses a classic condition instead of a reduce and math expression, then `$values` contains the combination of the `refID` and position of the condition. |
|
||||
| $value | The value string of the alert instance. For example, `[ var='A' labels={instance=foo} value=10 ]`. |
|
||||
|
||||
### Labels with dots
|
||||
|
||||
If a label contains a dot (full stop or period) in its name then the following will not work:
|
||||
|
||||
```
|
||||
Instance {{ $labels.instance.name }} has been down for more than 5 minutes
|
||||
```
|
||||
|
||||
This is because we are printing a non-existing field `name` in `$labels.instance` rather than `instance.name` in `$labels`. Instead we can use the `index` function to print `instance.name`:
|
||||
|
||||
```
|
||||
Instance {{ index $labels "instance.name" }} has been down for more than 5 minutes
|
||||
```
|
||||
|
||||
55
docs/sources/alerting/fundamentals/contact-points/index.md
Normal file
55
docs/sources/alerting/fundamentals/contact-points/index.md
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
aliases:
|
||||
- /docs/grafana/latest/alerting/contact-points/
|
||||
- /docs/grafana/latest/alerting/unified-alerting/contact-points/
|
||||
- /docs/grafana/latest/alerting/fundamentals/contact-points/contact-point-types/
|
||||
description: Create or edit contact point
|
||||
keywords:
|
||||
- grafana
|
||||
- alerting
|
||||
- guide
|
||||
- contact point
|
||||
- notification channel
|
||||
- create
|
||||
title: Contact points
|
||||
weight: 410
|
||||
---
|
||||
|
||||
# Contact points
|
||||
|
||||
Use contact points to define how your contacts are notified when an alert rule fires. A contact point can have one or more contact point types, for example, email, slack, webhook, and so on. When an alert rule fires, a notification is sent to all contact point types listed for a contact point. Contact points can be configured for the Grafana Alertmanager as well as external alertmanagers.
|
||||
|
||||
You can also use message templating to customize notification messages for contact point types.
|
||||
|
||||
## Supported contact point types
|
||||
|
||||
The following table lists the contact point types supported by Grafana.
|
||||
|
||||
| Name | Type | Grafana Alertmanager | Other Alertmanagers |
|
||||
| ------------------------------------------------ | ------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------- |
|
||||
| [DingDing](https://www.dingtalk.com/en) | `dingding` | Supported | N/A |
|
||||
| [Discord](https://discord.com/) | `discord` | Supported | N/A |
|
||||
| [Email](#email) | `email` | Supported | Supported |
|
||||
| [Google Hangouts](https://hangouts.google.com/) | `googlechat` | Supported | N/A |
|
||||
| [Kafka](https://kafka.apache.org/) | `kafka` | Supported | N/A |
|
||||
| [Line](https://line.me/en/) | `line` | Supported | N/A |
|
||||
| [Microsoft Teams](https://teams.microsoft.com/) | `teams` | Supported | N/A |
|
||||
| [Opsgenie](https://atlassian.com/opsgenie/) | `opsgenie` | Supported | Supported |
|
||||
| [Pagerduty](https://www.pagerduty.com/) | `pagerduty` | Supported | Supported |
|
||||
| [Prometheus Alertmanager](https://prometheus.io) | `prometheus-alertmanager` | Supported | N/A |
|
||||
| [Pushover](https://pushover.net/) | `pushover` | Supported | Supported |
|
||||
| [Sensu Go](https://docs.sensu.io/sensu-go/) | `sensugo` | Supported | N/A |
|
||||
| [Slack](https://slack.com/) | `slack` | Supported | Supported |
|
||||
| [Telegram](https://telegram.org/) | `telegram` | Supported | N/A |
|
||||
| [Threema](https://threema.ch/) | `threema` | Supported | N/A |
|
||||
| [VictorOps](https://help.victorops.com/) | `victorops` | Supported | Supported |
|
||||
| [Webhook](#webhook) | `webhook` | Supported | Supported ([different format](https://prometheus.io/docs/alerting/latest/configuration/#webhook_config)) |
|
||||
| [Cisco Webex Teams](#webex) | `webex` | Supported | Supported |
|
||||
| [WeCom](#wecom) | `wecom` | Supported | N/A |
|
||||
| [Zenduty](https://www.zenduty.com/) | `webhook` | Supported | N/A |
|
||||
|
||||
## Useful links
|
||||
|
||||
[Manage contact points](https://grafana.com/docs/grafana/next/alerting/manage-notifications/create-contact-point/)
|
||||
|
||||
[Create and edit message templates](https://grafana.com/docs/grafana/next/alerting/manage-notifications/create-message-template/)
|
||||
@@ -1,91 +0,0 @@
|
||||
---
|
||||
aliases:
|
||||
- /docs/grafana/latest/alerting/notifications/
|
||||
- /docs/grafana/latest/alerting/contact-points/
|
||||
- /docs/grafana/latest/alerting/unified-alerting/contact-points/
|
||||
- /docs/grafana/latest/alerting/fundamentals/contact-points/contact-point-types/
|
||||
description: Create or edit contact point
|
||||
keywords:
|
||||
- grafana
|
||||
- alerting
|
||||
- guide
|
||||
- contact point
|
||||
- notification channel
|
||||
- create
|
||||
title: Notifications
|
||||
weight: 410
|
||||
---
|
||||
|
||||
# Notifications
|
||||
|
||||
Notifications are sent when an alert is firing or has been resolved. You use notification policies to configure how and where a notification is sent; how often a notification should be sent; and whether alerts should all be sent in the same notification, sent in grouped notifications based on a set of labels, or as separate notifications.
|
||||
|
||||
## Notification policies
|
||||
|
||||
Notification policies control when and where notifications are sent. A notification policy can choose to send all alerts together in the same notification, send alerts in grouped notifications based on a set of labels, or send alerts as separate notifications. You can configure each notification policy to control how often notifications should be sent as well as having one or more mute timings to inhibit notifications at certain times of the day and on certain days of the week.
|
||||
|
||||
Notification policies are organized in a tree structure where at the root of the tree there is a notification policy called the root policy. There can be only one root policy and the root policy cannot be deleted.
|
||||
|
||||
Specific routing policies are descendents of the root policy and can be used to match either all alerts or a subset of alerts based on a set of matching labels. A notification policy matches an alert when its matching labels match the labels in the alert.
|
||||
|
||||
A specific routing policy can have its own descendent policies, called nested policies, which allow for additional matching of alerts. An example of a specific routing policy could be sending infrastructure alerts to the Ops team; while a descendent policy might send high priority alerts to Pagerduty and low priority alerts as emails.
|
||||
|
||||
All alerts, irrespective of their labels, match the root policy. However, when the root policy receives an alert it looks at each specific routing policy and sends the alert to the first specific routing policy that matches the alert. If the specific routing policy has further descendent policies, then it can attempt to the match the alert against one of its nested policies. If no nested policies match the alert then the specific routing policy is the matching policy. If there are no specific routing policies, or no specific routing policies match the alert, then the root policy is the matching policy.
|
||||
|
||||
More information on how to configure notification policies can be found [here]({{< relref "../../manage-notifications/create-notification-policy/" >}}).
|
||||
|
||||
## Contact points
|
||||
|
||||
Contact points contain the configuration for sending notifications. A contact point is a list of integrations, each of which sends a notification to a particular email address, service or URL. Contact points can have multiple integrations of the same kind, or a combination of integrations of different kinds. For example, a contact point could contain a Pagerduty integration; an email and Slack integration; or a Pagerduty integration, a Slack integration, and two email integrations. You can also configure a contact point with no integrations; in which case no notifications are sent.
|
||||
|
||||
A contact point cannot send notifications until it has been added to a notification policy. A notification policy can only send alerts to one contact point, but a contact point can be added to a number of notification policies at the same time. When an alert matches a notification policy, the alert is sent to the contact point in that notification policy, which then sends a notification to each integration in its configuration.
|
||||
|
||||
### Supported integrations
|
||||
|
||||
The following table contains the integrations supported in Grafana:
|
||||
|
||||
| Name | Type | Grafana Alertmanager | Other Alertmanagers |
|
||||
| ------------------------------------------------ | ------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------- |
|
||||
| [DingDing](https://www.dingtalk.com/en) | `dingding` | Supported | N/A |
|
||||
| [Discord](https://discord.com/) | `discord` | Supported | N/A |
|
||||
| [Email](#email) | `email` | Supported | Supported |
|
||||
| [Google Hangouts](https://hangouts.google.com/) | `googlechat` | Supported | N/A |
|
||||
| [Kafka](https://kafka.apache.org/) | `kafka` | Supported | N/A |
|
||||
| [Line](https://line.me/en/) | `line` | Supported | N/A |
|
||||
| [Microsoft Teams](https://teams.microsoft.com/) | `teams` | Supported | N/A |
|
||||
| [Opsgenie](https://atlassian.com/opsgenie/) | `opsgenie` | Supported | Supported |
|
||||
| [Pagerduty](https://www.pagerduty.com/) | `pagerduty` | Supported | Supported |
|
||||
| [Prometheus Alertmanager](https://prometheus.io) | `prometheus-alertmanager` | Supported | N/A |
|
||||
| [Pushover](https://pushover.net/) | `pushover` | Supported | Supported |
|
||||
| [Sensu Go](https://docs.sensu.io/sensu-go/) | `sensugo` | Supported | N/A |
|
||||
| [Slack](https://slack.com/) | `slack` | Supported | Supported |
|
||||
| [Telegram](https://telegram.org/) | `telegram` | Supported | N/A |
|
||||
| [Threema](https://threema.ch/) | `threema` | Supported | N/A |
|
||||
| [VictorOps](https://help.victorops.com/) | `victorops` | Supported | Supported |
|
||||
| [Webhook](#webhook) | `webhook` | Supported | Supported ([different format](https://prometheus.io/docs/alerting/latest/configuration/#webhook_config)) |
|
||||
| [Cisco Webex Teams](#webex) | `webex` | Supported | Supported |
|
||||
| [WeCom](#wecom) | `wecom` | Supported | N/A |
|
||||
| [Zenduty](https://www.zenduty.com/) | `webhook` | Supported | N/A |
|
||||
|
||||
## Templating notifications
|
||||
|
||||
You can customize notifications with templates. For example, templates can be used to change the subject and message of an email, or the title and message of notifications sent to Slack.
|
||||
|
||||
Templates are not limited to an individual integration or contact point, but instead can be used in a number of integrations in the same contact point and even integrations across different contact points. For example, a Grafana user can create a template called `custom_subject_or_title` and use it for both templating subjects in emails and titles of Slack messages without having to create two separate templates.
|
||||
|
||||
All notifications templates are written in [Go's templating language](https://pkg.go.dev/text/template), and are in the Contact points tab on the Alerting page.
|
||||
|
||||
More information on how to template notifications can be found [here]({{< relref "../../manage-notifications/create-message-template/" >}}).
|
||||
|
||||
## Silences
|
||||
|
||||
You can use silences to mute notifications from one or more firing rules. Silences do not stop alerts from firing or being resolved, or hide firing alerts in the user interface. A silence lasts as long as its duration which can be configured in minutes, hours, days, months or years.
|
||||
|
||||
More information on how to configure silences can be found [here]({{< relref "../../manage-notifications/create-silence/" >}}).
|
||||
|
||||
## Useful links
|
||||
|
||||
- [Notification policies]({{< relref "../../manage-notifications/create-notification-policy/" >}})
|
||||
- [Contact points]({{< relref "../../manage-notifications/create-contact-point/" >}})
|
||||
- [Templating notifications]({{< relref "../../manage-notifications/create-message-template/" >}})
|
||||
- [Silences]({{< relref "../../manage-notifications/create-silence/" >}})
|
||||
24
docs/sources/alerting/fundamentals/silences/index.md
Normal file
24
docs/sources/alerting/fundamentals/silences/index.md
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
aliases:
|
||||
- /docs/grafana/latest/alerting/silences/
|
||||
- /docs/grafana/latest/alerting/unified-alerting/silences/
|
||||
- /docs/grafana/latest/alerting/fundamentals/silences
|
||||
description: Silences
|
||||
keywords:
|
||||
- grafana
|
||||
- alerting
|
||||
- silence
|
||||
- mute
|
||||
title: Silences
|
||||
weight: 420
|
||||
---
|
||||
|
||||
# Silences
|
||||
|
||||
Use silences to stop notifications from one or more alerting rules. Silences do not prevent alert rules from being evaluated. They also do not stop alert instances from being shown in the user interface. Silences only stop notifications from getting created. A silence lasts for only a specified window of time.
|
||||
|
||||
You can configure Grafana managed silences as well as silences for an external Alertmanager data source.
|
||||
|
||||
## Useful links
|
||||
|
||||
[Create silences](https://grafana.com/docs/grafana/latest/alerting/manage-notifications/create-silence/)
|
||||
@@ -40,9 +40,11 @@ Public plugins need to be reviewed by the Grafana team before you can sign them.
|
||||
|
||||
```bash
|
||||
export GRAFANA_API_KEY=<YOUR_API_KEY>
|
||||
npx @grafana/sign-plugin
|
||||
npx @grafana/sign-plugin plugin:sign
|
||||
```
|
||||
|
||||
> **Note:** If running NPM 7+ the `npx` commands mentioned in this article may hang. The workaround is to use `npx --legacy-peer-deps <command to run>`.
|
||||
|
||||
## Sign a private plugin
|
||||
|
||||
1. In your plugin directory, sign the plugin with the API key you just created. Grafana Sign Plugin creates a [MANIFEST.txt](#plugin-manifest) file in the `dist` directory of your plugin.
|
||||
@@ -51,7 +53,7 @@ Public plugins need to be reviewed by the Grafana team before you can sign them.
|
||||
|
||||
```bash
|
||||
export GRAFANA_API_KEY=<YOUR_API_KEY>
|
||||
npx @grafana/sign-plugin --rootUrls https://example.com/grafana
|
||||
npx @grafana/sign-plugin plugin:sign --rootUrls https://example.com/grafana
|
||||
```
|
||||
|
||||
## Plugin signature levels
|
||||
|
||||
@@ -747,10 +747,6 @@ Text used as placeholder text on login page for password input.
|
||||
|
||||
Set the default UI theme: `dark` or `light`. Default is `dark`.
|
||||
|
||||
### default_language
|
||||
|
||||
This setting configures the default UI language, which must be a supported IETF language tag, such as `en-US`.
|
||||
|
||||
### home_page
|
||||
|
||||
Path to a custom home page. Users are only redirected to this if the default home dashboard is used. It should match a frontend route and contain a leading slash.
|
||||
|
||||
@@ -68,7 +68,6 @@ For a complete list of every change, with links to pull requests and related iss
|
||||
|
||||
## Grafana 9
|
||||
|
||||
- [What's new in 9.3]({{< relref "whats-new-in-v9-3/" >}})
|
||||
- [What's new in 9.2]({{< relref "whats-new-in-v9-2/" >}})
|
||||
- [What's new in 9.1]({{< relref "whats-new-in-v9-1/" >}})
|
||||
- [What's new in 9.0]({{< relref "whats-new-in-v9-0/" >}})
|
||||
|
||||
@@ -1,258 +0,0 @@
|
||||
---
|
||||
_build:
|
||||
list: false
|
||||
aliases:
|
||||
- /docs/grafana/latest/guides/whats-new-in-v9-3/
|
||||
description: Feature and improvement highlights for Grafana v9.3
|
||||
keywords:
|
||||
- grafana
|
||||
- new
|
||||
- documentation
|
||||
- '9.3'
|
||||
- release notes
|
||||
title: What's new in Grafana v9.3
|
||||
weight: -33
|
||||
---
|
||||
|
||||
# What’s new in Grafana v9.3
|
||||
|
||||
Welcome to Grafana 9.3! Read on to learn about our navigation overhaul, support for four new languages, new panels and transformations, several often-requested auth improvements, usability improvements to Alerting, and more. For even more detail about all the changes in this release, refer to the [changelog](https://github.com/grafana/grafana/blob/master/CHANGELOG.md).
|
||||
|
||||
## New navigation
|
||||
|
||||
Available in **beta** in all editions of Grafana
|
||||
|
||||
Use Grafana’s redesigned navigation to get full visibility into the health of your systems, by quickly jumping between features as part of your incident response workflow.
|
||||
|
||||
As Grafana has grown from a data visualization tool to an observability solution, we’ve added many new features along the way. This has resulted in pages that are visually inconsistent or hard to find. These updates to navigation give Grafana a new look and feel and make page layouts and navigation patterns more consistent.
|
||||
|
||||
We’ve revamped the navigation menu and grouped related tools together, making it easier to find what you need. Pages in Grafana now leverage new layouts that include breadcrumbs and a sidebar, allowing you to quickly jump between pages. We’ve also introduced a header that appears on all pages in Grafana, making dashboard search accessible from any page.
|
||||
|
||||
To try out Grafana’s new navigation, enable the `topnav` feature toggle. If you are a Cloud Advanced customer, open a ticket with our support team and we will enable it for you.
|
||||
|
||||
**Note:** The Grafana and Grafana Cloud documentation has not yet been updated to reflect changes to the navigation - these changes will roll out when the new navigation becomes generally available.
|
||||
|
||||
{{< figure src="/static/img/docs/navigation/navigation-9-3.png" max-width="750px" caption="New navigation for Grafana" >}}
|
||||
|
||||
## View dashboards in Spanish, French, German, and Simplified Chinese
|
||||
|
||||
Generally available in all editions of Grafana
|
||||
|
||||
We have added four new languages to Grafana: Spanish, French, German, and Simplified Chinese.
|
||||
|
||||
With millions of users across the globe, Grafana has a global footprint. In order to make it accessible to a wider audience, we have taken the first steps in localizing key workflows. You can now set Grafana’s language for the navigation, viewing dashboards, and some settings. This will cover the main activities a Viewer performs within Grafana.
|
||||
|
||||
Read more about configuring the [default language for your organization]({{< relref "../administration/organization-preferences/" >}}) and [updating your profile]({{< relref "../administration/user-management/user-preferences/" >}}) in our documentation.
|
||||
|
||||
{{< figure src="/static/img/docs/internationalization/internationalization-9-3.png" max-width="750px" caption="Grafana available in Spanish, French, German, and Simplified Chinese" >}}
|
||||
|
||||
## Geomap panel
|
||||
|
||||
Generally available in all editions of Grafana
|
||||
|
||||
We have added a new alpha layer type in Geomap called photo layer. This layer enables you to render a photo at each data point. To learn more about the photo layer and the geomap panel, refer to [Photos layer]({{< relref "../panels-visualizations/visualizations/geomap/#photos-layer-alpha" >}}).
|
||||
|
||||
{{< figure src="/static/img/docs/geomap-panel/geomap-photos-9-3-0.png" max-width="750px" caption="Geomap panel photos layer" >}}
|
||||
|
||||
## Canvas panel
|
||||
|
||||
Available in **beta** in all editions of Grafana
|
||||
|
||||
Canvas is a new panel that combines the power of Grafana with the flexibility of custom elements. Canvas visualizations are extensible form-built panels that allow you to explicitly place elements within static and dynamic layouts. This empowers you to design custom visualizations and overlay data in ways that aren’t possible with standard Grafana panels, all within Grafana’s UI. If you’ve used popular UI and web design tools, then designing Canvas panels will feel very familiar.
|
||||
|
||||
In Grafana v9.3, we have added icon value mapping support to the Canvas panel. This enables you to dynamically set which icon to display based on your data. To learn more about the Canvas panel, refer to [Canvas]({{< relref "../panels-visualizations/visualizations/canvas" >}}).
|
||||
|
||||
{{< video-embed src="/static/img/docs/canvas-panel/canvas-icon-value-mapping-support-9-3-0.mp4" max-width="750px" caption="Canvas panel icon value mapping support" >}}
|
||||
|
||||
## Public dashboards improvements
|
||||
|
||||
We've made the following improvements to public dashboards.
|
||||
|
||||
### Manage all of your public dashboards in one place
|
||||
|
||||
Available in **experimental** in Grafana Open Source, Enterprise, and Cloud Advanced
|
||||
|
||||
You can use Public Dashboards to make a given dashboard available to anyone on the internet without needing to sign in. In Grafana v9.3, we have introduced a new screen where you can manage all of your public dashboards. From here, you can view a list of all of the public dashboards in your Grafana instance, navigate to the underlying dashboard, see if it is enabled, link out to the public version of the dashboard, or update the public dashboard's configuration. You can see a public dashboard's configuration if you have view access to the dashboard itself, and you can edit its configuration if you have the Admin or Server Admin role or the "Public Dashboard writer" role if you are using RBAC in Grafana Enterprise or Cloud Advanced.
|
||||
|
||||
To check out this new screen and configure your public dashboards, navigate to **Dashboards > Public Dashboards**.
|
||||
|
||||
### Choose to display annotations in public dashboards
|
||||
|
||||
Available in **experimental** in Grafana Open Source, Enterprise, and Cloud Advanced
|
||||
|
||||
Annotations are now supported in public dashboards, with the exception of query annotations. They are turned off by default, but can be turned on in your public dashboard settings.
|
||||
|
||||
Note that because Public Dashboards is an experimental feature, you need to enable it in Grafana using the `publicDashboards` [feature toggle]({{< relref "../setup-grafana/configure-grafana/#feature_toggles" >}}), or open a support ticket requesting public dashboards if you are a Cloud Advanced customer.
|
||||
|
||||
To learn more about public dashboards, refer to [Public dashboards]({{< relref "../dashboards/dashboard-public/" >}}).
|
||||
|
||||
## New transformation: Partition by values
|
||||
|
||||
Available in **experimental** in all editions of Grafana
|
||||
|
||||
This new transformation can help eliminate the need for multiple queries to the same datasource with different WHERE clauses when graphing multiple series.
|
||||
|
||||
Consider a metrics SQL table with the following data:
|
||||
|
||||
| Time | Region | Value |
|
||||
| ------------------- | ------ | ----- |
|
||||
| 2022-10-20 12:00:00 | US | 1520 |
|
||||
| 2022-10-20 12:00:00 | EU | 2936 |
|
||||
| 2022-10-20 01:00:00 | US | 1327 |
|
||||
| 2022-10-20 01:00:00 | EU | 912 |
|
||||
|
||||
Prior to v9.3, if you wanted to plot a red trendline for US and a blue one for EU in the same TimeSeries panel, you would likely have to split this into two queries:
|
||||
|
||||
```
|
||||
SELECT Time, Value FROM metrics WHERE Time > ‘2022-10-20’ AND Region=’US’
|
||||
SELECT Time, Value FROM metrics WHERE Time > ‘2022-10-20’ AND Region=’EU’
|
||||
```
|
||||
|
||||
This approach also requires you to know ahead of time which regions exist in the metrics table.
|
||||
|
||||
With the partition by values transformer, you can issue a single query and split the results by unique (enum) values from one or more columns (fields) of your choosing. In this case, Region.
|
||||
|
||||
```
|
||||
SELECT Time, Region, Value FROM metrics WHERE Time > ‘2022-10-20’
|
||||
```
|
||||
|
||||
| Time | Region | Value |
|
||||
| ------------------- | ------ | ----- |
|
||||
| 2022-10-20 12:00:00 | US | 1520 |
|
||||
| 2022-10-20 01:00:00 | US | 1327 |
|
||||
|
||||
| Time | Region | Value |
|
||||
| ------------------- | ------ | ----- |
|
||||
| 2022-10-20 12:00:00 | EU | 2936 |
|
||||
| 2022-10-20 01:00:00 | EU | 912 |
|
||||
|
||||
## Reporting: Zoom in and out to fit your data better into a PDF
|
||||
|
||||
Generally available in Grafana Enterprise, Cloud Pro, and Cloud Advanced.
|
||||
|
||||
Because dashboards appear on a screen and reports are PDFs, it can be challenging to render data just the way you want to. Sometimes the report doesn't show enough columns in a table, or the titles appear too small. Now you can adjust the scale of your report to zoom in and make each text field and panel larger or zoom out to show more data.
|
||||
|
||||
The zoom feature is located in the **Format Report** section of your reporting configuration. To learn more about reporting, refer to [Create and manage reports]({{< relref "../dashboards/create-reports/">}}).
|
||||
|
||||
{{< figure src="/static/img/docs/enterprise/reports/report-zoom.png" max-width="750px" caption="Report zoom feature with PDF documents at three different zoom levels" >}}
|
||||
|
||||
## Users and access
|
||||
|
||||
We've made the following improvements to users and access.
|
||||
|
||||
### OAuth: token handling improvements
|
||||
|
||||
Generally available in all editions of Grafana
|
||||
|
||||
As part of our efforts to improve the security of Grafana, we are introducing a long-awaited feature that enhances Grafana's OAuth 2.0 compatibility. When a user logs in using an OAuth provider, Grafana verifies on each request that the user's access token has not expired. Grafana uses the refresh token provided (if any exists) when an access token expires to obtain a new access token.
|
||||
|
||||
Because this feature introduces a breaking change, it is behind the `accessTokenExpirationCheck` feature toggle and is disabled by default. Enabling this functionality without configuring refresh tokens for the specific OAuth provider will sign users out after their access token has expired, and they would need to sign in again every time.
|
||||
|
||||
Complete documentation on how to configure obtaining a refresh token can be found on the [authentication configuration page]({{< relref "../setup-grafana/configure-security/configure-authentication/" >}}), in the instructions for your Oauth identity provider.
|
||||
|
||||
### Resolve user conflicts in Grafana's CLI
|
||||
|
||||
In the older versions of Grafana, usernames were case-sensitive. This created conflicts, where a user might sign in using two different methods (like SAML and OAuth) and have two accounts created, like `elastigirl@incredibles.com` and `ElastiGirl@incredibles.com`. Users in this situation might think they have lost their preferences and permissions. If this has occurred in your Grafana instance, you can use a new Grafana CLI command to resolve user identity conflicts between users within Grafana.
|
||||
|
||||
> Note: If you use Grafana Cloud or you run Grafana with MySQL as your database, you will not experience any user identity conflicts and you do not need to use this tool.
|
||||
|
||||
```bash
|
||||
# lists all the conflicting users
|
||||
$ grafana-cli user-manager conflicts list
|
||||
|
||||
# creates a conflict patch file to edit
|
||||
$ grafana-cli user-manager conflicts generate-file
|
||||
|
||||
# reads edited conflict patch file for validation
|
||||
$ grafana-cli user-manager conflicts validate-file <filepath>
|
||||
|
||||
# ingests the conflict users file. Can be executed once per file and will change the state of the database.
|
||||
$ grafana-cli user-manager conflicts ingest-file <filepath>
|
||||
```
|
||||
|
||||
### LDAP: Role mapping improvements
|
||||
|
||||
Generally available in all editions of Grafana
|
||||
|
||||
If you use an LDAP directory to authenticate to Grafana but prefer to assign organizations and roles in the Grafana UI
|
||||
or via API, you can now skip user organization role synchronization with your LDAP
|
||||
directory.
|
||||
|
||||
Use the `skip_org_role_sync` [LDAP authentication configuration option]({{< relref
|
||||
"../setup-grafana/configure-security/configure-authentication/ldap/#disable-org-role-synchronization" >}})
|
||||
when configuring LDAP authentication to prevent the synchronization between your LDAP groups and organization roles
|
||||
and make user roles editable manually.
|
||||
|
||||
### Azure AD OAuth2: New option to always fetch groups from the Graph API
|
||||
|
||||
Generally available in all editions of Grafana
|
||||
|
||||
If you use Azure AD OAuth2 authentication and use `SecurityEnabled` groups that you don't want Azure to embed in the
|
||||
authentication token, you can configure Grafana to use Microsoft's Graph API instead.
|
||||
|
||||
Use the [`force_use_graph_api` configuration option]({{< relref
|
||||
"../setup-grafana/configure-security/configure-authentication/azuread/#force-fetching-groups-from-microsoft-graph-api" >}})
|
||||
when configuring Azure AD authentication to force Grafana to fetch groups using Graph API.
|
||||
|
||||
### RBAC: List token's permissions
|
||||
|
||||
Generally available in Grafana Enterprise and Cloud Advanced
|
||||
|
||||
We added a new endpoint to help users diagnose permissions-related issues with user and token authorization.
|
||||
[This endpoint]({{< relref "../developers/http_api/access_control/#list-your-permissions" >}}) allows users to get the
|
||||
full list of RBAC permissions associated with their token.
|
||||
|
||||
For more details, refer to [Debug the permissions of a service account token]({{< relref
|
||||
"../administration/service-accounts/#debug-the-permissions-of-a-service-account-token" >}}).
|
||||
|
||||
### RBAC with Terraform: Extended support for provisioning permissions
|
||||
|
||||
Generally available in Grafana Enterprise and Cloud Advanced
|
||||
|
||||
All Grafana users can now use the latest release of [Terraform's Grafana provider](https://registry.terraform.io/providers/grafana/grafana/latest/docs) (version 1.31.1+) to provision [user and team access to service accounts]({{< relref "../administration/service-accounts/#manage-users-and-teams-permissions-for-a-service-account-in-grafana" >}}).
|
||||
|
||||
This allows full management of service accounts through Terraform - from creating a service account and allowing users to access it to assigning roles to the service account and generating service account tokens.
|
||||
|
||||
Grafana Enterprise and Cloud Pro and Advanced users can now provision [access to data sources]({{< relref "../administration/data-source-management/#data-source-permissions" >}}) for Grafana's `Viewer`, `Editor`, and `Admin` basic roles, as well as assign `Edit` permission.
|
||||
|
||||
We have also added [documentation on provisioning RBAC roles and role assignments]({{< relref "../administration/roles-and-permissions/access-control/rbac-terraform-provisioning/" >}}) to guide our Grafana Enterprise and Cloud Pro and Advanced users through this process.
|
||||
|
||||
Finally, we have fixed several access control related bugs to ensure a smoother provisioning experience.
|
||||
|
||||
## Alerting
|
||||
|
||||
All of these new alerting features are generally available in all editions of Grafana.
|
||||
|
||||
### Email templating
|
||||
|
||||
We've improved the design and functionality of email templates to make template creation much easier and more customizable. The email template framework utilizes MJML to define and compile the final email HTML output. Sprig functions in the email templates provide more customizable template functions.
|
||||
|
||||
{{< figure src="/static/img/docs/alerting/alert-templates-whats-new-v9.3.png" max-width="750px" caption="Email template redesign" >}}
|
||||
|
||||
### Support for Webex Teams
|
||||
|
||||
You can now use Cisco Webex Teams as a contact point, to send alerts to a Webex Teams channel.
|
||||
|
||||
### Edit alert rules created using the provisioning API
|
||||
|
||||
Edit API-provisioned alert rules from the Grafana UI. To make a provisioned alert editable, add the `x-disable-provenance` header to the following requests when creating or editing your alert rules in the API:
|
||||
|
||||
POST /api/v1/provisioning/alert-rules
|
||||
|
||||
PUT /api/v1/provisioning/alert-rules/{UID}
|
||||
|
||||
### Support values in notification templates
|
||||
|
||||
Add alert values to notification templates, so that you can create a single template that prints the annotations, labels, and values for your alerts in a format of your choice.
|
||||
|
||||
### View notification errors
|
||||
|
||||
When an alert fails to fire, see when something is wrong with your contact point(s) and the reason for the error. The Receivers API contains information on the error, including a time stamp, duration of the attempt, and the error. You can also view the errors for each contact point in the UI.
|
||||
|
||||
{{< figure src="/static/img/docs/alerting/alert-view-notification-errors-whats-new-v9.3.png" max-width="750px" caption="Alert notification errors" >}}
|
||||
|
||||
### Redesign of the expressions pipeline
|
||||
|
||||
We've redesigned the expressions pipeline editor to combine the expressions editor and the preview into a single view.
|
||||
|
||||
{{< figure src="/static/img/docs/alerting/alert-expression-pipeline-whats-new-v9.3.png" max-width="750px" caption="Expression pipeline redesign" >}}
|
||||
@@ -4,5 +4,5 @@
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "9.3.0"
|
||||
"version": "9.3.0-beta.1"
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"license": "AGPL-3.0-only",
|
||||
"private": true,
|
||||
"name": "grafana",
|
||||
"version": "9.3.0",
|
||||
"version": "9.3.0-beta.1",
|
||||
"repository": "github:grafana/grafana",
|
||||
"scripts": {
|
||||
"build": "yarn i18n:compile && NODE_ENV=production webpack --config scripts/webpack/webpack.prod.js",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/data",
|
||||
"version": "9.3.0",
|
||||
"version": "9.3.0-beta.1",
|
||||
"description": "Grafana Data Library",
|
||||
"keywords": [
|
||||
"typescript"
|
||||
@@ -30,13 +30,11 @@
|
||||
"scripts": {
|
||||
"build": "tsc -p ./tsconfig.build.json && rollup -c rollup.config.ts",
|
||||
"clean": "rimraf ./dist ./compiled ./package.tgz",
|
||||
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
|
||||
"prepack": "cp package.json package.json.bak && node ../../scripts/prepare-packagejson.js",
|
||||
"postpack": "mv package.json.bak package.json"
|
||||
"typecheck": "tsc --emitDeclarationOnly false --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "6.0.1",
|
||||
"@grafana/schema": "9.3.0",
|
||||
"@grafana/schema": "9.3.0-beta.1",
|
||||
"@types/d3-interpolate": "^1.4.0",
|
||||
"d3-interpolate": "1.4.0",
|
||||
"date-fns": "2.29.3",
|
||||
|
||||
@@ -101,26 +101,6 @@ describe('field convert type', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('can convert strings with commas to numbers', () => {
|
||||
const options = { targetField: 'stringy nums', destinationType: FieldType.number };
|
||||
|
||||
const stringyNumbers = {
|
||||
name: 'stringy nums',
|
||||
type: FieldType.string,
|
||||
values: new ArrayVector(['1,000', '1,000,000']),
|
||||
config: {},
|
||||
};
|
||||
|
||||
const numbers = convertFieldType(stringyNumbers, options);
|
||||
|
||||
expect(numbers).toEqual({
|
||||
name: 'stringy nums',
|
||||
type: FieldType.number,
|
||||
values: new ArrayVector([1000, 1000000]),
|
||||
config: {},
|
||||
});
|
||||
});
|
||||
|
||||
describe('field convert types transformer', () => {
|
||||
beforeAll(() => {
|
||||
mockTransformationsRegistry([convertFieldTypeTransformer]);
|
||||
|
||||
@@ -142,9 +142,7 @@ function fieldToNumberField(field: Field): Field {
|
||||
const numValues = field.values.toArray().slice();
|
||||
|
||||
for (let n = 0; n < numValues.length; n++) {
|
||||
// some numbers returned from datasources have commas
|
||||
// strip the commas, coerce the string to a number
|
||||
const number = +numValues[n].replace(/,/g, '');
|
||||
const number = +numValues[n];
|
||||
numValues[n] = Number.isFinite(number) ? number : null;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/e2e-selectors",
|
||||
"version": "9.3.0",
|
||||
"version": "9.3.0-beta.1",
|
||||
"description": "Grafana End-to-End Test Selectors Library",
|
||||
"keywords": [
|
||||
"cli",
|
||||
@@ -34,9 +34,7 @@
|
||||
"build": "tsc -p ./tsconfig.build.json && rollup -c rollup.config.ts",
|
||||
"bundle": "rollup -c rollup.config.ts",
|
||||
"clean": "rimraf ./dist ./compiled ./package.tgz",
|
||||
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
|
||||
"prepack": "cp package.json package.json.bak && node ../../scripts/prepare-packagejson.js",
|
||||
"postpack": "mv package.json.bak package.json"
|
||||
"typecheck": "tsc --emitDeclarationOnly false --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "23.0.2",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/e2e",
|
||||
"version": "9.3.0",
|
||||
"version": "9.3.0-beta.1",
|
||||
"description": "Grafana End-to-End Test Library",
|
||||
"keywords": [
|
||||
"cli",
|
||||
@@ -42,9 +42,7 @@
|
||||
"start": "cypress run --browser=chrome",
|
||||
"start-benchmark": "CYPRESS_NO_COMMAND_LOG=1 yarn start",
|
||||
"test": "pushd test && node ../dist/bin/grafana-e2e.js run",
|
||||
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
|
||||
"prepack": "cp package.json package.json.bak && node ../../scripts/prepare-packagejson.js",
|
||||
"postpack": "mv package.json.bak package.json"
|
||||
"typecheck": "tsc --emitDeclarationOnly false --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "15.0.1",
|
||||
@@ -63,7 +61,7 @@
|
||||
"@babel/core": "7.19.6",
|
||||
"@babel/preset-env": "7.19.4",
|
||||
"@cypress/webpack-preprocessor": "5.15.2",
|
||||
"@grafana/e2e-selectors": "9.3.0",
|
||||
"@grafana/e2e-selectors": "9.3.0-beta.1",
|
||||
"@grafana/tsconfig": "^1.2.0-rc1",
|
||||
"@mochajs/json-file-reporter": "^1.2.0",
|
||||
"babel-loader": "9.1.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/runtime",
|
||||
"version": "9.3.0",
|
||||
"version": "9.3.0-beta.1",
|
||||
"description": "Grafana Runtime Library",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -32,15 +32,13 @@
|
||||
"build": "tsc -p ./tsconfig.build.json && rollup -c rollup.config.ts",
|
||||
"bundle": "rollup -c rollup.config.ts",
|
||||
"clean": "rimraf ./dist ./compiled ./package.tgz",
|
||||
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
|
||||
"prepack": "cp package.json package.json.bak && node ../../scripts/prepare-packagejson.js",
|
||||
"postpack": "mv package.json.bak package.json"
|
||||
"typecheck": "tsc --emitDeclarationOnly false --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grafana/data": "9.3.0",
|
||||
"@grafana/e2e-selectors": "9.3.0",
|
||||
"@grafana/data": "9.3.0-beta.1",
|
||||
"@grafana/e2e-selectors": "9.3.0-beta.1",
|
||||
"@grafana/faro-web-sdk": "1.0.0-beta2",
|
||||
"@grafana/ui": "9.3.0",
|
||||
"@grafana/ui": "9.3.0-beta.1",
|
||||
"@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.3.0",
|
||||
"version": "9.3.0-beta.1",
|
||||
"description": "Grafana Schema Library",
|
||||
"keywords": [
|
||||
"typescript"
|
||||
@@ -31,9 +31,7 @@
|
||||
"build": "tsc -p ./tsconfig.build.json && rollup -c rollup.config.ts",
|
||||
"bundle": "rollup -c rollup.config.ts",
|
||||
"clean": "rimraf ./dist ./compiled ./package.tgz",
|
||||
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
|
||||
"prepack": "cp package.json package.json.bak && node ../../scripts/prepare-packagejson.js",
|
||||
"postpack": "mv package.json.bak package.json"
|
||||
"typecheck": "tsc --emitDeclarationOnly false --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@grafana/tsconfig": "^1.2.0-rc1",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/toolkit",
|
||||
"version": "9.3.0",
|
||||
"version": "9.3.0-beta.1",
|
||||
"description": "Grafana Toolkit",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -51,10 +51,10 @@
|
||||
"@babel/preset-env": "7.18.9",
|
||||
"@babel/preset-react": "7.18.6",
|
||||
"@babel/preset-typescript": "7.18.6",
|
||||
"@grafana/data": "9.3.0",
|
||||
"@grafana/data": "9.3.0-beta.1",
|
||||
"@grafana/eslint-config": "5.0.0",
|
||||
"@grafana/tsconfig": "^1.2.0-rc1",
|
||||
"@grafana/ui": "9.3.0",
|
||||
"@grafana/ui": "9.3.0-beta.1",
|
||||
"@jest/core": "27.5.1",
|
||||
"@types/command-exists": "^1.2.0",
|
||||
"@types/eslint": "8.4.1",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/ui",
|
||||
"version": "9.3.0",
|
||||
"version": "9.3.0-beta.1",
|
||||
"description": "Grafana Components Library",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -38,9 +38,7 @@
|
||||
"storybook": "start-storybook -p 9001 -c .storybook",
|
||||
"storybook:build": "build-storybook -o ./dist/storybook -c .storybook",
|
||||
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
|
||||
"generate-icons-bundle-cache-file": "node ./scripts/generate-icon-bundle.js",
|
||||
"prepack": "cp package.json package.json.bak && node ../../scripts/prepare-packagejson.js",
|
||||
"postpack": "mv package.json.bak package.json"
|
||||
"generate-icons-bundle-cache-file": "node ./scripts/generate-icon-bundle.js"
|
||||
},
|
||||
"browserslist": [
|
||||
"defaults",
|
||||
@@ -49,9 +47,9 @@
|
||||
"dependencies": {
|
||||
"@emotion/css": "11.10.5",
|
||||
"@emotion/react": "11.10.5",
|
||||
"@grafana/data": "9.3.0",
|
||||
"@grafana/e2e-selectors": "9.3.0",
|
||||
"@grafana/schema": "9.3.0",
|
||||
"@grafana/data": "9.3.0-beta.1",
|
||||
"@grafana/e2e-selectors": "9.3.0-beta.1",
|
||||
"@grafana/schema": "9.3.0-beta.1",
|
||||
"@leeoniya/ufuzzy": "0.8.0",
|
||||
"@monaco-editor/react": "4.4.6",
|
||||
"@popperjs/core": "2.11.6",
|
||||
|
||||
@@ -54,7 +54,7 @@ const getStyles = (theme: GrafanaTheme2, color: BadgeColor) => {
|
||||
} else {
|
||||
bgColor = tinycolor(sourceColor).setAlpha(0.15).toString();
|
||||
borderColor = tinycolor(sourceColor).lighten(20).toString();
|
||||
textColor = tinycolor(sourceColor).darken(20).toString();
|
||||
textColor = tinycolor(sourceColor).darken(15).toString();
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -305,10 +305,6 @@ export const Table = memo((props: Props) => {
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setExpandedIndexes(new Set());
|
||||
}, [data, subData]);
|
||||
|
||||
const renderSubTable = React.useCallback(
|
||||
(rowIndex: number) => {
|
||||
if (expandedIndexes.has(rowIndex)) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jaegertracing/jaeger-ui-components",
|
||||
"version": "9.3.0",
|
||||
"version": "9.3.0-beta.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -31,10 +31,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/css": "11.10.5",
|
||||
"@grafana/data": "9.3.0",
|
||||
"@grafana/e2e-selectors": "9.3.0",
|
||||
"@grafana/runtime": "9.3.0",
|
||||
"@grafana/ui": "9.3.0",
|
||||
"@grafana/data": "9.3.0-beta.1",
|
||||
"@grafana/e2e-selectors": "9.3.0-beta.1",
|
||||
"@grafana/runtime": "9.3.0-beta.1",
|
||||
"@grafana/ui": "9.3.0-beta.1",
|
||||
"chance": "^1.0.10",
|
||||
"classnames": "^2.2.5",
|
||||
"combokeys": "^3.0.0",
|
||||
|
||||
@@ -130,6 +130,11 @@ func (hs *HTTPServer) CreateDashboardSnapshot(c *models.ReqContext) response.Res
|
||||
|
||||
metrics.MApiDashboardSnapshotExternal.Inc()
|
||||
} else {
|
||||
if cmd.Dashboard.Get("id").MustInt64() == 0 {
|
||||
c.JSON(http.StatusBadRequest, "Creating a local snapshot requires a dashboard")
|
||||
return nil
|
||||
}
|
||||
|
||||
if cmd.Key == "" {
|
||||
var err error
|
||||
cmd.Key, err = util.GetRandomString(32)
|
||||
|
||||
@@ -392,7 +392,6 @@ func executeFPM(options linuxPackageOptions, packageRoot, srcDir string) error {
|
||||
switch options.packageType {
|
||||
case packageTypeRpm:
|
||||
args = append(args, "-t", "rpm", "--rpm-posttrans", "packaging/rpm/control/posttrans")
|
||||
args = append(args, "--rpm-digest", "sha256")
|
||||
case packageTypeDeb:
|
||||
args = append(args, "-t", "deb", "--deb-no-default-config-files")
|
||||
default:
|
||||
|
||||
@@ -8,8 +8,10 @@ import (
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"cuelang.org/go/cue/errors"
|
||||
"github.com/grafana/grafana"
|
||||
"github.com/grafana/grafana/pkg/cuectx"
|
||||
"github.com/grafana/thema"
|
||||
tload "github.com/grafana/thema/load"
|
||||
)
|
||||
|
||||
// CoreStructuredDeclParentPath is the path, relative to the repository root, where
|
||||
@@ -46,14 +48,28 @@ func loadpFrameworkOnce() {
|
||||
})
|
||||
}
|
||||
|
||||
var prefix = filepath.Join("/pkg", "kindsys")
|
||||
|
||||
func doLoadFrameworkCUE(ctx *cue.Context) (cue.Value, error) {
|
||||
v, err := cuectx.BuildGrafanaInstance(ctx, filepath.Join("pkg", "kindsys"), "kindsys", nil)
|
||||
var v cue.Value
|
||||
var err error
|
||||
|
||||
absolutePath := prefix
|
||||
if !filepath.IsAbs(absolutePath) {
|
||||
absolutePath, err = filepath.Abs(absolutePath)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
}
|
||||
|
||||
bi, err := tload.InstancesWithThema(grafana.CueSchemaFS, absolutePath)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
v = ctx.BuildInstance(bi)
|
||||
|
||||
if err = v.Validate(cue.Concrete(false), cue.All()); err != nil {
|
||||
return cue.Value{}, fmt.Errorf("kindsys framework loaded cue.Value has err: %w", err)
|
||||
return cue.Value{}, fmt.Errorf("coremodel framework loaded cue.Value has err: %w", err)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
|
||||
@@ -3,7 +3,6 @@ package models
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
@@ -12,8 +11,7 @@ import (
|
||||
|
||||
// Typed errors
|
||||
var (
|
||||
ErrUserTokenNotFound = errors.New("user token not found")
|
||||
ErrInvalidSessionToken = errors.New("invalid session token")
|
||||
ErrUserTokenNotFound = errors.New("user token not found")
|
||||
)
|
||||
|
||||
// CreateTokenErr represents a token creation error; used in Enterprise
|
||||
@@ -35,11 +33,7 @@ type TokenExpiredError struct {
|
||||
TokenID int64
|
||||
}
|
||||
|
||||
func (e *TokenExpiredError) Unwrap() error { return ErrInvalidSessionToken }
|
||||
|
||||
func (e *TokenExpiredError) Error() string {
|
||||
return fmt.Sprintf("%s: user token expired", ErrInvalidSessionToken)
|
||||
}
|
||||
func (e *TokenExpiredError) Error() string { return "user token expired" }
|
||||
|
||||
type TokenRevokedError struct {
|
||||
UserID int64
|
||||
@@ -47,11 +41,7 @@ type TokenRevokedError struct {
|
||||
MaxConcurrentSessions int64
|
||||
}
|
||||
|
||||
func (e *TokenRevokedError) Error() string {
|
||||
return fmt.Sprintf("%s: user token revoked", ErrInvalidSessionToken)
|
||||
}
|
||||
|
||||
func (e *TokenRevokedError) Unwrap() error { return ErrInvalidSessionToken }
|
||||
func (e *TokenRevokedError) Error() string { return "user token revoked" }
|
||||
|
||||
// UserToken represents a user token
|
||||
type UserToken struct {
|
||||
|
||||
@@ -434,12 +434,9 @@ func (h *ContextHandler) initContextWithToken(reqContext *models.ReqContext, org
|
||||
|
||||
token, err := h.AuthTokenService.LookupToken(ctx, rawToken)
|
||||
if err != nil {
|
||||
reqContext.Logger.Warn("failed to look up session from cookie", "error", err)
|
||||
if errors.Is(err, models.ErrUserTokenNotFound) || errors.Is(err, models.ErrInvalidSessionToken) {
|
||||
// Burn the cookie in case of invalid, expired or missing token
|
||||
reqContext.Resp.Before(h.deleteInvalidCookieEndOfRequestFunc(reqContext))
|
||||
}
|
||||
|
||||
reqContext.Logger.Warn("Failed to look up user based on cookie", "error", err)
|
||||
// Burn the cookie in case of failure
|
||||
reqContext.Resp.Before(h.deleteInvalidCookieEndOfRequestFunc(reqContext))
|
||||
reqContext.LookupTokenErr = err
|
||||
|
||||
return false
|
||||
|
||||
@@ -49,7 +49,7 @@ func (dc *CacheServiceImpl) GetDatasource(
|
||||
}
|
||||
}
|
||||
|
||||
dc.logger.FromContext(ctx).Debug("Querying for data source via SQL store", "id", datasourceID, "orgId", user.OrgID)
|
||||
dc.logger.Debug("Querying for data source via SQL store", "id", datasourceID, "orgId", user.OrgID)
|
||||
|
||||
query := &datasources.GetDataSourceQuery{Id: datasourceID, OrgId: user.OrgID}
|
||||
ss := SqlStore{db: dc.SQLStore, logger: dc.logger}
|
||||
@@ -90,7 +90,7 @@ func (dc *CacheServiceImpl) GetDatasourceByUID(
|
||||
}
|
||||
}
|
||||
|
||||
dc.logger.FromContext(ctx).Debug("Querying for data source via SQL store", "uid", datasourceUID, "orgId", user.OrgID)
|
||||
dc.logger.Debug("Querying for data source via SQL store", "uid", datasourceUID, "orgId", user.OrgID)
|
||||
query := &datasources.GetDataSourceQuery{Uid: datasourceUID, OrgId: user.OrgID}
|
||||
ss := SqlStore{db: dc.SQLStore, logger: dc.logger}
|
||||
err := ss.GetDataSource(ctx, query)
|
||||
|
||||
BIN
pkg/services/folder/folderimpl/test.db
Normal file
BIN
pkg/services/folder/folderimpl/test.db
Normal file
Binary file not shown.
@@ -209,40 +209,6 @@ func (pn *PushoverNotifier) genPushoverBody(ctx context.Context, as ...*types.Al
|
||||
return nil, b, fmt.Errorf("failed write the message: %w", err)
|
||||
}
|
||||
|
||||
pn.writeImageParts(ctx, w, as...)
|
||||
|
||||
var sound string
|
||||
if status == model.AlertResolved {
|
||||
sound = tmpl(pn.settings.okSound)
|
||||
} else {
|
||||
sound = tmpl(pn.settings.alertingSound)
|
||||
}
|
||||
if sound != "default" {
|
||||
if err := w.WriteField("sound", sound); err != nil {
|
||||
return nil, b, fmt.Errorf("failed to write the sound: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the message as HTML
|
||||
if err := w.WriteField("html", "1"); err != nil {
|
||||
return nil, b, fmt.Errorf("failed to mark the message as HTML: %w", err)
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
return nil, b, fmt.Errorf("failed to close the multipart request: %w", err)
|
||||
}
|
||||
|
||||
if tmplErr != nil {
|
||||
pn.log.Warn("failed to template pushover message", "error", tmplErr.Error())
|
||||
}
|
||||
|
||||
headers := map[string]string{
|
||||
"Content-Type": w.FormDataContentType(),
|
||||
}
|
||||
|
||||
return headers, b, nil
|
||||
}
|
||||
|
||||
func (pn *PushoverNotifier) writeImageParts(ctx context.Context, w *multipart.Writer, as ...*types.Alert) {
|
||||
// Pushover supports at most one image attachment with a maximum size of pushoverMaxFileSize.
|
||||
// If the image is larger than pushoverMaxFileSize then return an error.
|
||||
_ = withStoredImages(ctx, pn.log, pn.images, func(index int, image ngmodels.Image) error {
|
||||
@@ -276,4 +242,34 @@ func (pn *PushoverNotifier) writeImageParts(ctx context.Context, w *multipart.Wr
|
||||
|
||||
return ErrImagesDone
|
||||
}, as...)
|
||||
|
||||
var sound string
|
||||
if status == model.AlertResolved {
|
||||
sound = tmpl(pn.settings.okSound)
|
||||
} else {
|
||||
sound = tmpl(pn.settings.alertingSound)
|
||||
}
|
||||
if sound != "default" {
|
||||
if err := w.WriteField("sound", sound); err != nil {
|
||||
return nil, b, fmt.Errorf("failed to write the sound: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the message as HTML
|
||||
if err := w.WriteField("html", "1"); err != nil {
|
||||
return nil, b, fmt.Errorf("failed to mark the message as HTML: %w", err)
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
return nil, b, fmt.Errorf("failed to close the multipart request: %w", err)
|
||||
}
|
||||
|
||||
if tmplErr != nil {
|
||||
pn.log.Warn("failed to template pushover message", "error", tmplErr.Error())
|
||||
}
|
||||
|
||||
headers := map[string]string{
|
||||
"Content-Type": w.FormDataContentType(),
|
||||
}
|
||||
|
||||
return headers, b, nil
|
||||
}
|
||||
|
||||
@@ -82,8 +82,7 @@ func getImage(ctx context.Context, l log.Logger, imageStore ImageStore, alert ty
|
||||
// images have been found.
|
||||
func withStoredImages(ctx context.Context, l log.Logger, imageStore ImageStore, forEachFunc forEachImageFunc, alerts ...*types.Alert) error {
|
||||
for index, alert := range alerts {
|
||||
logger := l.New("alert", alert.String())
|
||||
img, err := getImage(ctx, logger, imageStore, *alert)
|
||||
img, err := getImage(ctx, l, imageStore, *alert)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if img != nil {
|
||||
@@ -91,7 +90,6 @@ func withStoredImages(ctx context.Context, l log.Logger, imageStore ImageStore,
|
||||
if errors.Is(err, ErrImagesDone) {
|
||||
return nil
|
||||
}
|
||||
logger.Error("Failed to attach image to notification", "error", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,10 +119,9 @@ func (m dashboardPermissionsMigrator) migratePermissions(dashboards []dashboard,
|
||||
m.mapPermission(d.ID, models.PERMISSION_VIEW, d.IsFolder)...,
|
||||
)
|
||||
} else {
|
||||
for _, a := range deduplicateAcl(acls) {
|
||||
roleName := getRoleName(a)
|
||||
permissionMap[d.OrgID][roleName] = append(
|
||||
permissionMap[d.OrgID][roleName],
|
||||
for _, a := range acls {
|
||||
permissionMap[d.OrgID][getRoleName(a)] = append(
|
||||
permissionMap[d.OrgID][getRoleName(a)],
|
||||
m.mapPermission(d.ID, a.Permission, d.IsFolder)...,
|
||||
)
|
||||
}
|
||||
@@ -225,39 +224,6 @@ func getRoleName(p models.DashboardACL) string {
|
||||
return fmt.Sprintf("managed:builtins:%s:permissions", strings.ToLower(string(*p.Role)))
|
||||
}
|
||||
|
||||
func deduplicateAcl(acl []models.DashboardACL) []models.DashboardACL {
|
||||
output := make([]models.DashboardACL, 0, len(acl))
|
||||
uniqueACL := map[string]models.DashboardACL{}
|
||||
for _, item := range acl {
|
||||
// acl items with userID or teamID is enforced to be unique by sql constraint, so we can skip those
|
||||
if item.UserID > 0 || item.TeamID > 0 {
|
||||
output = append(output, item)
|
||||
continue
|
||||
}
|
||||
|
||||
// better to make sure so we don't panic
|
||||
if item.Role == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
current, ok := uniqueACL[string(*item.Role)]
|
||||
if !ok {
|
||||
uniqueACL[string(*item.Role)] = item
|
||||
continue
|
||||
}
|
||||
|
||||
if current.Permission < item.Permission {
|
||||
uniqueACL[string(*item.Role)] = item
|
||||
}
|
||||
}
|
||||
|
||||
for _, item := range uniqueACL {
|
||||
output = append(output, item)
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
var _ migrator.CodeMigration = new(dashboardUidPermissionMigrator)
|
||||
|
||||
type dashboardUidPermissionMigrator struct {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@grafana-plugins/input-datasource",
|
||||
"version": "9.3.0",
|
||||
"version": "9.3.0-beta.1",
|
||||
"description": "Input Datasource",
|
||||
"private": true,
|
||||
"repository": {
|
||||
@@ -15,15 +15,15 @@
|
||||
},
|
||||
"author": "Grafana Labs",
|
||||
"devDependencies": {
|
||||
"@grafana/toolkit": "9.3.0",
|
||||
"@grafana/toolkit": "9.3.0-beta.1",
|
||||
"@types/jest": "26.0.15",
|
||||
"@types/lodash": "4.14.149",
|
||||
"@types/react": "17.0.30",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grafana/data": "9.3.0",
|
||||
"@grafana/ui": "9.3.0",
|
||||
"@grafana/data": "9.3.0-beta.1",
|
||||
"@grafana/ui": "9.3.0-beta.1",
|
||||
"jquery": "3.5.1",
|
||||
"react": "17.0.1",
|
||||
"react-dom": "17.0.1",
|
||||
|
||||
@@ -138,26 +138,6 @@ describe('FolderPicker', () => {
|
||||
|
||||
expect(pickerOptions[0]).not.toHaveTextContent('General');
|
||||
});
|
||||
|
||||
it('should return the correct search results when typing in the select', async () => {
|
||||
jest.spyOn(api, 'searchFolders').mockImplementation((query: string) => {
|
||||
return Promise.resolve(
|
||||
[
|
||||
{ title: 'Dash Test', uid: 'xMsQdBfWz' } as DashboardSearchHit,
|
||||
{ title: 'Dash Two', uid: 'wfTJJL5Wz' } as DashboardSearchHit,
|
||||
].filter((dash) => dash.title.indexOf(query) > -1)
|
||||
);
|
||||
});
|
||||
jest.spyOn(contextSrv, 'hasAccess').mockReturnValue(false);
|
||||
const onChangeFn = jest.fn();
|
||||
render(<FolderPicker onChange={onChangeFn} />);
|
||||
|
||||
const pickerContainer = screen.getByLabelText(selectors.components.FolderPicker.input);
|
||||
await userEvent.type(pickerContainer, 'Test');
|
||||
|
||||
expect(await screen.findByText('Dash Test')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Dash Two')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getInitialValues', () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from '@emotion/css';
|
||||
import debounce from 'debounce-promise';
|
||||
import { debounce } from 'lodash';
|
||||
import React, { useState, useEffect, useMemo, useCallback, FormEvent } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
|
||||
@@ -112,11 +112,9 @@ export class UserAdminPage extends PureComponent<Props> {
|
||||
user?.isExternal && user?.authLabels?.some((r) => SyncedOAuthLabels.includes(r));
|
||||
const isSAMLUser = user?.isExternal && user?.authLabels?.includes('SAML');
|
||||
const isGoogleUser = user?.isExternal && user?.authLabels?.includes('Google');
|
||||
const isAuthProxyUser = user?.isExternal && user?.authLabels?.includes('Auth Proxy');
|
||||
const isUserSynced =
|
||||
!config.auth.DisableSyncLock &&
|
||||
((user?.isExternal &&
|
||||
!(isAuthProxyUser || isGoogleUser || isOAuthUserWithSkippableSync || isSAMLUser || isLDAPUser)) ||
|
||||
((user?.isExternal && !(isGoogleUser || isOAuthUserWithSkippableSync || isSAMLUser || isLDAPUser)) ||
|
||||
(!config.auth.OAuthSkipOrgRoleUpdateSync && isOAuthUserWithSkippableSync) ||
|
||||
(!config.auth.SAMLSkipOrgRoleSync && isSAMLUser) ||
|
||||
(!config.auth.LDAPSkipOrgRoleSync && isLDAPUser));
|
||||
|
||||
@@ -29,7 +29,6 @@ export const CollapseToggle: FC<Props> = ({
|
||||
<Button
|
||||
type="button"
|
||||
fill="text"
|
||||
variant="secondary"
|
||||
aria-expanded={!isCollapsed}
|
||||
aria-controls={idControlled}
|
||||
className={cx(styles.expandButton, className)}
|
||||
@@ -44,6 +43,7 @@ export const CollapseToggle: FC<Props> = ({
|
||||
|
||||
export const getStyles = (theme: GrafanaTheme2) => ({
|
||||
expandButton: css`
|
||||
color: ${theme.colors.text.secondary};
|
||||
margin-right: ${theme.spacing(1)};
|
||||
`,
|
||||
});
|
||||
|
||||
@@ -25,7 +25,6 @@ export const AlertGroup = ({ alertManagerSourceName, group }: Props) => {
|
||||
<div className={styles.header}>
|
||||
<div className={styles.group} data-testid="alert-group">
|
||||
<CollapseToggle
|
||||
size="sm"
|
||||
isCollapsed={isCollapsed}
|
||||
onToggle={() => setIsCollapsed(!isCollapsed)}
|
||||
data-testid="alert-group-collapse-toggle"
|
||||
|
||||
@@ -192,7 +192,6 @@ export const RulesGroup: FC<Props> = React.memo(({ group, namespace, expandAll,
|
||||
<div className={styles.wrapper} data-testid="rule-group">
|
||||
<div className={styles.header} data-testid="rule-group-header">
|
||||
<CollapseToggle
|
||||
size="sm"
|
||||
className={styles.collapseToggle}
|
||||
isCollapsed={isCollapsed}
|
||||
onToggle={setIsCollapsed}
|
||||
|
||||
@@ -4,8 +4,9 @@ import { useLocalStorage } from 'react-use';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { Button, Counter, useStyles2 } from '@grafana/ui';
|
||||
import { Counter, useStyles2 } from '@grafana/ui';
|
||||
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||
import { CollapseToggle } from 'app/features/alerting/unified/components/CollapseToggle';
|
||||
|
||||
import { PANEL_EDITOR_UI_STATE_STORAGE_KEY } from './state/reducers';
|
||||
|
||||
@@ -106,16 +107,7 @@ export const OptionsPaneCategory: FC<OptionsPaneCategoryProps> = React.memo(
|
||||
ref={ref}
|
||||
>
|
||||
<div className={headerStyles} onClick={onToggle} aria-label={selectors.components.OptionsGroup.toggle(id)}>
|
||||
<Button
|
||||
type="button"
|
||||
fill="text"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
aria-expanded={isExpanded}
|
||||
className={styles.toggleButton}
|
||||
icon={isExpanded ? 'angle-down' : 'angle-right'}
|
||||
onClick={onToggle}
|
||||
/>
|
||||
<CollapseToggle isCollapsed={!isExpanded} idControlled={id} onToggle={onToggle} />
|
||||
<h6 id={`button-${id}`} className={styles.title}>
|
||||
{renderTitle(isExpanded)}
|
||||
</h6>
|
||||
@@ -145,14 +137,13 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
overflow: hidden;
|
||||
line-height: 1.5;
|
||||
font-size: 1rem;
|
||||
padding-left: 6px;
|
||||
font-weight: ${theme.typography.fontWeightMedium};
|
||||
margin: 0;
|
||||
`,
|
||||
header: css`
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
align-items: baseline;
|
||||
padding: ${theme.spacing(0.5)};
|
||||
color: ${theme.colors.text.primary};
|
||||
font-weight: ${theme.typography.fontWeightMedium};
|
||||
@@ -161,9 +152,6 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
background: ${theme.colors.emphasize(theme.colors.background.primary, 0.03)};
|
||||
}
|
||||
`,
|
||||
toggleButton: css`
|
||||
align-self: baseline;
|
||||
`,
|
||||
headerExpanded: css`
|
||||
color: ${theme.colors.text.primary};
|
||||
`,
|
||||
|
||||
@@ -142,7 +142,7 @@ describe('Explore: Query History', () => {
|
||||
await assertQueryHistory(['{"expr":"query #2"}', '{"expr":"query #1"}']);
|
||||
});
|
||||
|
||||
it.skip('updates the state in both Explore panes', async () => {
|
||||
it('updates the state in both Explore panes', async () => {
|
||||
const urlParams = {
|
||||
left: serializeStateToUrlParam({
|
||||
datasource: 'loki',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from '@emotion/css';
|
||||
import debounce from 'debounce-promise';
|
||||
import { debounce } from 'lodash';
|
||||
import React, { MouseEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2, SelectableValue, urlUtil } from '@grafana/data';
|
||||
@@ -31,7 +31,10 @@ export function OpenLibraryPanelModal({ libraryPanel, onDismiss }: OpenLibraryPa
|
||||
(searchString: string) => loadOptionsAsync(libraryPanel.uid, searchString, setLoading),
|
||||
[libraryPanel.uid]
|
||||
);
|
||||
const debouncedLoadOptions = useMemo(() => debounce(loadOptions, 300, { leading: true }), [loadOptions]);
|
||||
const debouncedLoadOptions = useMemo(
|
||||
() => debounce(loadOptions, 300, { leading: true, trailing: true }),
|
||||
[loadOptions]
|
||||
);
|
||||
const onViewPanel = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
locationService.push(urlUtil.renderUrl(`/d/${option?.value?.uid}`, {}));
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
import { AnyAction, createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import React, { useCallback, useEffect, useMemo, useReducer } from 'react';
|
||||
import { createHtmlPortalNode, InPortal, OutPortal } from 'react-reverse-portal';
|
||||
import { useLocation, useRouteMatch } from 'react-router-dom';
|
||||
import { useLocation, useRouteMatch, useParams } from 'react-router-dom';
|
||||
|
||||
import { AppEvents, AppPlugin, AppPluginMeta, NavModel, NavModelItem, PluginType } from '@grafana/data';
|
||||
import { config, locationSearchToObject } from '@grafana/runtime';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { getNotFoundNav, getWarningNav, getExceptionNav } from 'app/angular/services/nav_model_srv';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
import PageLoader from 'app/core/components/PageLoader/PageLoader';
|
||||
@@ -35,13 +35,13 @@ const initialState: State = { loading: true, pluginNav: null, plugin: null };
|
||||
|
||||
export function AppRootPage({ pluginId, pluginNavSection }: Props) {
|
||||
const match = useRouteMatch();
|
||||
const queryParams = useParams();
|
||||
const location = useLocation();
|
||||
const [state, dispatch] = useReducer(stateSlice.reducer, initialState);
|
||||
const portalNode = useMemo(() => createHtmlPortalNode(), []);
|
||||
const currentUrl = config.appSubUrl + location.pathname + location.search;
|
||||
const { plugin, loading, pluginNav } = state;
|
||||
const navModel = buildPluginSectionNav(pluginNavSection, pluginNav, currentUrl);
|
||||
const queryParams = useMemo(() => locationSearchToObject(location.search), [location.search]);
|
||||
const context = useMemo(() => buildPluginPageContext(navModel), [navModel]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { QueryFormat } from '../types';
|
||||
|
||||
import migrateAnnotation from './migration';
|
||||
|
||||
describe('Annotation migration', () => {
|
||||
const annotation = {
|
||||
datasource: {
|
||||
uid: 'P4FDCC188E688367F',
|
||||
type: 'mysql',
|
||||
},
|
||||
enable: false,
|
||||
hide: false,
|
||||
iconColor: 'rgba(0, 211, 255, 1)',
|
||||
limit: 100,
|
||||
name: 'Single',
|
||||
rawQuery:
|
||||
"SELECT\n createdAt as time,\n 'single' as text,\n hostname as tags\nFROM\n grafana_metric\nWHERE\n $__timeFilter(createdAt)\nORDER BY time\nLIMIT 1\n",
|
||||
showIn: 0,
|
||||
tags: [],
|
||||
type: 'tags',
|
||||
};
|
||||
|
||||
it('should migrate from old format to new', () => {
|
||||
const newAnnotationFormat = migrateAnnotation(annotation);
|
||||
expect(newAnnotationFormat.target?.format).toBe(QueryFormat.Table);
|
||||
expect(newAnnotationFormat.target?.rawSql).toBe(annotation.rawQuery);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AnnotationQuery } from '@grafana/data';
|
||||
import { EditorMode } from '@grafana/experimental';
|
||||
|
||||
import { applyQueryDefaults } from '../defaults';
|
||||
import { SQLQuery } from '../types';
|
||||
|
||||
export default function migrateAnnotation(annotation: AnnotationQuery<SQLQuery>) {
|
||||
@@ -10,7 +10,12 @@ export default function migrateAnnotation(annotation: AnnotationQuery<SQLQuery>)
|
||||
return annotation;
|
||||
}
|
||||
|
||||
const newQuery = applyQueryDefaults({ refId: 'Annotation', ...(annotation.target ?? {}), rawSql: oldQuery });
|
||||
const newQuery: SQLQuery = {
|
||||
...(annotation.target ?? {}),
|
||||
refId: annotation.target?.refId ?? 'Anno',
|
||||
editorMode: EditorMode.Code,
|
||||
rawSql: oldQuery,
|
||||
};
|
||||
|
||||
return {
|
||||
...annotation,
|
||||
|
||||
@@ -189,7 +189,7 @@ export class TempoDatasource extends DataSourceWithBackend<TempoQuery, TempoJson
|
||||
}).pipe(
|
||||
map((response) => {
|
||||
return {
|
||||
data: createTableFrameFromTraceQlQuery(response.data.traces, this.instanceSettings),
|
||||
data: [createTableFrameFromTraceQlQuery(response.data.traces, this.instanceSettings)],
|
||||
};
|
||||
}),
|
||||
catchError((error) => {
|
||||
|
||||
@@ -117,34 +117,47 @@ describe('createTableFrameFromSearch()', () => {
|
||||
expect(frame.fields[2].values.get(0)).toBe('2022-01-28 03:00:28');
|
||||
expect(frame.fields[2].values.get(1)).toBe('2022-01-27 22:56:06');
|
||||
|
||||
expect(frame.fields[3].name).toBe('traceDuration');
|
||||
expect(frame.fields[3].name).toBe('duration');
|
||||
expect(frame.fields[3].values.get(0)).toBe(65);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createTableFrameFromTraceQlQuery()', () => {
|
||||
test('transforms TraceQL response to DataFrame', () => {
|
||||
const frameList = createTableFrameFromTraceQlQuery(traceQlResponse.traces, defaultSettings);
|
||||
const frame = frameList[0];
|
||||
const frame = createTableFrameFromTraceQlQuery(traceQlResponse.traces, defaultSettings);
|
||||
// Trace ID field
|
||||
expect(frame.fields[0].name).toBe('traceID');
|
||||
expect(frame.fields[0].values.get(0)).toBe('b1586c3c8c34d');
|
||||
expect(frame.fields[0].config.unit).toBe('string');
|
||||
expect(frame.fields[0].values).toBeInstanceOf(ArrayVector);
|
||||
// Trace name field
|
||||
expect(frame.fields[1].name).toBe('traceName');
|
||||
expect(frame.fields[1].type).toBe('string');
|
||||
expect(frame.fields[1].values.get(0)).toBe('lb HTTP Client');
|
||||
// There should be a traceIdHidden field which is hidden
|
||||
expect(frame.fields[1].name).toBe('traceIdHidden');
|
||||
expect(frame.fields[1].config).toBeDefined();
|
||||
expect(frame.fields[1].config.custom).toStrictEqual({ hidden: true });
|
||||
expect(frame.fields[1].values).toBeInstanceOf(ArrayVector);
|
||||
// Start time field
|
||||
expect(frame.fields[2].name).toBe('startTime');
|
||||
expect(frame.fields[2].type).toBe('string');
|
||||
expect(frame.fields[2].values.get(1)).toBe('2022-01-27 22:56:06');
|
||||
// Span ID field
|
||||
expect(frame.fields[2].name).toBe('spanID');
|
||||
expect(frame.fields[2].config.unit).toBe('string');
|
||||
expect(frame.fields[2].values).toBeInstanceOf(ArrayVector);
|
||||
// Trace name field
|
||||
expect(frame.fields[3].name).toBe('traceName');
|
||||
expect(frame.fields[3].type).toBe('string');
|
||||
expect(frame.fields[3].values.get(0)).toBe('lb HTTP Client');
|
||||
expect(frame.fields[3].values).toBeInstanceOf(ArrayVector);
|
||||
// Start time field
|
||||
expect(frame.fields[4].name).toBe('startTime');
|
||||
expect(frame.fields[4].type).toBe('string');
|
||||
expect(frame.fields[4].values.get(1)).toBe('2022-10-19 09:03:34');
|
||||
expect(frame.fields[4].values).toBeInstanceOf(ArrayVector);
|
||||
// Duration field
|
||||
expect(frame.fields[3].name).toBe('traceDuration');
|
||||
expect(frame.fields[3].type).toBe('number');
|
||||
expect(frame.fields[3].values.get(2)).toBe(44);
|
||||
expect(frame.fields[5].name).toBe('duration');
|
||||
expect(frame.fields[5].type).toBe('number');
|
||||
expect(frame.fields[5].values.get(2)).toBe(6686000);
|
||||
// There should be a field for each attribute
|
||||
expect(frame.fields[6].name).toBe('http.method');
|
||||
expect(frame.fields[6].values).toBeInstanceOf(ArrayVector);
|
||||
expect(frame.fields[7].name).toBe('service.name');
|
||||
expect(frame.fields[7].values).toBeInstanceOf(ArrayVector);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { SpanStatus, SpanStatusCode } from '@opentelemetry/api';
|
||||
import { collectorTypes } from '@opentelemetry/exporter-collector';
|
||||
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
|
||||
import differenceInHours from 'date-fns/differenceInHours';
|
||||
import formatDistance from 'date-fns/formatDistance';
|
||||
|
||||
import {
|
||||
DataFrame,
|
||||
@@ -13,7 +15,6 @@ import {
|
||||
TraceSpanReference,
|
||||
TraceSpanRow,
|
||||
dateTimeFormat,
|
||||
FieldDTO,
|
||||
} from '@grafana/data';
|
||||
|
||||
import { createGraphFrames } from './graphTransform';
|
||||
@@ -576,7 +577,7 @@ export function createTableFrameFromSearch(data: TraceSearchMetadata[], instance
|
||||
},
|
||||
{ name: 'traceName', type: FieldType.string, config: { displayNameFromDS: 'Trace name' } },
|
||||
{ name: 'startTime', type: FieldType.string, config: { displayNameFromDS: 'Start time' } },
|
||||
{ name: 'traceDuration', type: FieldType.number, config: { displayNameFromDS: 'Duration', unit: 'ms' } },
|
||||
{ name: 'duration', type: FieldType.number, config: { displayNameFromDS: 'Duration', unit: 'ns' } },
|
||||
],
|
||||
meta: {
|
||||
preferredVisualisationType: 'table',
|
||||
@@ -612,8 +613,8 @@ function transformToTraceData(data: TraceSearchMetadata) {
|
||||
|
||||
return {
|
||||
traceID: data.traceID,
|
||||
startTime,
|
||||
traceDuration: data.durationMs?.toString(),
|
||||
startTime: startTime,
|
||||
duration: data.durationMs?.toString(),
|
||||
traceName,
|
||||
};
|
||||
}
|
||||
@@ -621,7 +622,7 @@ function transformToTraceData(data: TraceSearchMetadata) {
|
||||
export function createTableFrameFromTraceQlQuery(
|
||||
data: TraceSearchMetadata[],
|
||||
instanceSettings: DataSourceInstanceSettings
|
||||
): DataFrame[] {
|
||||
) {
|
||||
const frame = new MutableDataFrame({
|
||||
fields: [
|
||||
{
|
||||
@@ -646,58 +647,6 @@ export function createTableFrameFromTraceQlQuery(
|
||||
],
|
||||
},
|
||||
},
|
||||
{ name: 'traceName', type: FieldType.string, config: { displayNameFromDS: 'Name' } },
|
||||
{ name: 'startTime', type: FieldType.string, config: { displayNameFromDS: 'Start time' } },
|
||||
{ name: 'traceDuration', type: FieldType.number, config: { displayNameFromDS: 'Duration', unit: 'ms' } },
|
||||
],
|
||||
meta: {
|
||||
preferredVisualisationType: 'table',
|
||||
},
|
||||
});
|
||||
|
||||
if (!data?.length) {
|
||||
return [frame];
|
||||
}
|
||||
|
||||
const subDataFrames: DataFrame[] = [];
|
||||
const tableRows = data
|
||||
// Show the most recent traces
|
||||
.sort((a, b) => parseInt(b?.startTimeUnixNano!, 10) / 1000000 - parseInt(a?.startTimeUnixNano!, 10) / 1000000)
|
||||
.reduce((rows: TraceTableData[], trace, currentIndex) => {
|
||||
const traceData: TraceTableData = transformToTraceData(trace);
|
||||
rows.push(traceData);
|
||||
subDataFrames.push(traceSubFrame(trace, instanceSettings, currentIndex));
|
||||
return rows;
|
||||
}, []);
|
||||
|
||||
for (const row of tableRows) {
|
||||
frame.add(row);
|
||||
}
|
||||
|
||||
return [frame, ...subDataFrames];
|
||||
}
|
||||
|
||||
const traceSubFrame = (
|
||||
trace: TraceSearchMetadata,
|
||||
instanceSettings: DataSourceInstanceSettings,
|
||||
currentIndex: number
|
||||
): DataFrame => {
|
||||
const spanDynamicAttrs: Record<string, FieldDTO> = {};
|
||||
let hasNameAttribute = false;
|
||||
trace.spanSet?.spans.forEach((span) => {
|
||||
if (span.name) {
|
||||
hasNameAttribute = true;
|
||||
}
|
||||
span.attributes?.forEach((attr) => {
|
||||
spanDynamicAttrs[attr.key] = {
|
||||
name: attr.key,
|
||||
type: FieldType.string,
|
||||
config: { displayNameFromDS: attr.key },
|
||||
};
|
||||
});
|
||||
});
|
||||
const subFrame = new MutableDataFrame({
|
||||
fields: [
|
||||
{
|
||||
name: 'traceIdHidden',
|
||||
config: {
|
||||
@@ -721,82 +670,85 @@ const traceSubFrame = (
|
||||
query: '${__data.fields.traceIdHidden}',
|
||||
queryType: 'traceId',
|
||||
},
|
||||
panelsState: {
|
||||
trace: {
|
||||
spanId: '${__value.raw}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
type: FieldType.string,
|
||||
config: { displayNameFromDS: 'Name', custom: { hidden: !hasNameAttribute } },
|
||||
},
|
||||
{
|
||||
name: 'spanStartTime',
|
||||
type: FieldType.string,
|
||||
config: { displayNameFromDS: 'Start time' },
|
||||
},
|
||||
...Object.values(spanDynamicAttrs),
|
||||
{
|
||||
name: 'duration',
|
||||
type: FieldType.number,
|
||||
config: { displayNameFromDS: 'Duration', unit: 'ns' },
|
||||
},
|
||||
{ name: 'traceName', type: FieldType.string, config: { displayNameFromDS: 'Trace name' } },
|
||||
// { name: 'attributes', type: FieldType.string, config: { displayNameFromDS: 'Attributes' } },
|
||||
{ name: 'startTime', type: FieldType.string, config: { displayNameFromDS: 'Start time' } },
|
||||
{ name: 'duration', type: FieldType.number, config: { displayNameFromDS: 'Duration', unit: 'ns' } },
|
||||
],
|
||||
meta: {
|
||||
preferredVisualisationType: 'table',
|
||||
custom: {
|
||||
parentRowIndex: currentIndex,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!data?.length) {
|
||||
return frame;
|
||||
}
|
||||
|
||||
trace.spanSet?.spans.forEach((span) => {
|
||||
subFrame.add(transformSpanToTraceData(span, trace.traceID));
|
||||
const attributesAdded: string[] = [];
|
||||
|
||||
data.forEach((trace) => {
|
||||
trace.spanSet?.spans.forEach((span) => {
|
||||
span.attributes?.forEach((attr) => {
|
||||
if (!attributesAdded.includes(attr.key)) {
|
||||
frame.addField({ name: attr.key, type: FieldType.string, config: { displayNameFromDS: attr.key } });
|
||||
attributesAdded.push(attr.key);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return subFrame;
|
||||
};
|
||||
const tableRows = data
|
||||
// Show the most recent traces
|
||||
.sort((a, b) => parseInt(b?.startTimeUnixNano!, 10) / 1000000 - parseInt(a?.startTimeUnixNano!, 10) / 1000000)
|
||||
.reduce((rows: TraceTableData[], trace) => {
|
||||
const traceData: TraceTableData = transformToTraceData(trace);
|
||||
rows.push(traceData);
|
||||
trace.spanSet?.spans.forEach((span) => {
|
||||
rows.push(transformSpanToTraceData(span, trace.traceID));
|
||||
});
|
||||
return rows;
|
||||
}, []);
|
||||
|
||||
for (const row of tableRows) {
|
||||
frame.add(row);
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
interface TraceTableData {
|
||||
[key: string]: string | number | boolean | undefined; // dynamic attribute name
|
||||
[key: string]: string | number | undefined; // dynamic attribute name
|
||||
traceID?: string;
|
||||
spanID?: string;
|
||||
//attributes?: string;
|
||||
startTime?: string;
|
||||
name?: string;
|
||||
traceDuration?: string;
|
||||
duration?: string;
|
||||
}
|
||||
|
||||
function transformSpanToTraceData(span: Span, traceID: string): TraceTableData {
|
||||
const spanStartTimeUnixMs = parseInt(span.startTimeUnixNano, 10) / 1000000;
|
||||
let spanStartTime = dateTimeFormat(spanStartTimeUnixMs);
|
||||
|
||||
if (Math.abs(differenceInHours(new Date(spanStartTime), Date.now())) <= 1) {
|
||||
spanStartTime = formatDistance(new Date(spanStartTime), Date.now(), {
|
||||
addSuffix: true,
|
||||
includeSeconds: true,
|
||||
});
|
||||
}
|
||||
|
||||
const data: TraceTableData = {
|
||||
traceIdHidden: traceID,
|
||||
spanID: span.spanID,
|
||||
spanStartTime,
|
||||
duration: parseInt(span.durationNanos, 10),
|
||||
name: span.name,
|
||||
startTime: spanStartTime,
|
||||
duration: span.durationNanos,
|
||||
};
|
||||
|
||||
span.attributes?.forEach((attr) => {
|
||||
if (attr.value.boolValue) {
|
||||
data[attr.key] = attr.value.boolValue;
|
||||
}
|
||||
if (attr.value.doubleValue) {
|
||||
data[attr.key] = attr.value.doubleValue;
|
||||
}
|
||||
if (attr.value.intValue) {
|
||||
data[attr.key] = attr.value.intValue;
|
||||
}
|
||||
if (attr.value.stringValue) {
|
||||
data[attr.key] = attr.value.stringValue;
|
||||
}
|
||||
data[attr.key] = attr.value.stringValue;
|
||||
});
|
||||
|
||||
return data;
|
||||
|
||||
@@ -2210,8 +2210,6 @@ export const traceQlResponse = {
|
||||
traceID: 'b1586c3c8c34d',
|
||||
rootServiceName: 'lb',
|
||||
rootTraceName: 'HTTP Client',
|
||||
startTimeUnixNano: '1643356828724000000',
|
||||
durationMs: 65,
|
||||
spanSet: {
|
||||
spans: [
|
||||
{
|
||||
@@ -2298,8 +2296,6 @@ export const traceQlResponse = {
|
||||
traceID: '9161e77388f3e',
|
||||
rootServiceName: 'lb',
|
||||
rootTraceName: 'HTTP Client',
|
||||
startTimeUnixNano: '1643342166678000000',
|
||||
durationMs: 93,
|
||||
spanSet: {
|
||||
spans: [
|
||||
{
|
||||
@@ -2386,8 +2382,6 @@ export const traceQlResponse = {
|
||||
traceID: '480691f7c6f20',
|
||||
rootServiceName: 'lb',
|
||||
rootTraceName: 'HTTP Client',
|
||||
startTimeUnixNano: '1643342166678000000',
|
||||
durationMs: 44,
|
||||
spanSet: {
|
||||
spans: [
|
||||
{
|
||||
|
||||
@@ -33,6 +33,7 @@ export function TraceQLEditor(props: Props) {
|
||||
language={langId}
|
||||
onBlur={onChange}
|
||||
onChange={onChange}
|
||||
height={'30px'}
|
||||
containerStyles={styles.queryField}
|
||||
monacoOptions={{
|
||||
folding: false,
|
||||
@@ -54,7 +55,6 @@ export function TraceQLEditor(props: Props) {
|
||||
setupAutocompleteFn(editor, monaco);
|
||||
setupActions(editor, monaco, onRunQuery);
|
||||
setupPlaceholder(editor, monaco, styles);
|
||||
setupAutoSize(editor);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -92,30 +92,19 @@ function setupActions(editor: monacoTypes.editor.IStandaloneCodeEditor, monaco:
|
||||
editor.addAction({
|
||||
id: 'run-query',
|
||||
label: 'Run Query',
|
||||
|
||||
keybindings: [monaco.KeyMod.Shift | monaco.KeyCode.Enter],
|
||||
|
||||
contextMenuGroupId: 'navigation',
|
||||
|
||||
contextMenuOrder: 1.5,
|
||||
|
||||
run: function () {
|
||||
onRunQuery();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function setupAutoSize(editor: monacoTypes.editor.IStandaloneCodeEditor) {
|
||||
const container = editor.getDomNode();
|
||||
const updateHeight = () => {
|
||||
if (container) {
|
||||
const contentHeight = Math.min(1000, editor.getContentHeight());
|
||||
const width = parseInt(container.style.width, 10);
|
||||
container.style.width = `${width}px`;
|
||||
container.style.height = `${contentHeight}px`;
|
||||
editor.layout({ width, height: contentHeight });
|
||||
}
|
||||
};
|
||||
editor.onDidContentSizeChange(updateHeight);
|
||||
updateHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook that returns function that will set up monaco autocomplete for the label selector
|
||||
* @param datasource
|
||||
|
||||
@@ -128,22 +128,13 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP
|
||||
}));
|
||||
case 'SPANSET_IN_VALUE':
|
||||
const tagName = this.overrideTagName(situation.tagName);
|
||||
const tagsNoQuotesAroundValue: string[] = ['status'];
|
||||
const tagValues = await this.getTagValues(tagName);
|
||||
const items: Completion[] = [];
|
||||
|
||||
const getInsertionText = (val: SelectableValue<string>): string => {
|
||||
if (situation.betweenQuotes) {
|
||||
return val.label!;
|
||||
}
|
||||
return tagsNoQuotesAroundValue.includes(situation.tagName) ? val.label! : `"${val.label}"`;
|
||||
};
|
||||
|
||||
tagValues.forEach((val) => {
|
||||
if (val?.label) {
|
||||
items.push({
|
||||
label: val.label,
|
||||
insertText: getInsertionText(val),
|
||||
insertText: situation.betweenQuotes ? val.label : `"${val.label}"`,
|
||||
type: 'TAG_VALUE',
|
||||
});
|
||||
}
|
||||
@@ -194,17 +185,17 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP
|
||||
|
||||
// prettier-ignore
|
||||
const fullRegex = new RegExp(
|
||||
'([\\s{])' + // Space(s) or initial opening bracket {
|
||||
'(' + // Open full set group
|
||||
nameRegex.source +
|
||||
'(?<space1>\\s*)' + // Optional space(s) between name and operator
|
||||
'(' + // Open operator + value group
|
||||
opRegex.source +
|
||||
'(?<space2>\\s*)' + // Optional space(s) between operator and value
|
||||
valueRegex.source +
|
||||
')?' + // Close operator + value group
|
||||
')' + // Close full set group
|
||||
'(?<space3>\\s*)$' // Optional space(s) at the end of the set
|
||||
'([\\s{])' + // Space(s) or initial opening bracket {
|
||||
'(' + // Open full set group
|
||||
nameRegex.source +
|
||||
'(?<space1>\\s*)' + // Optional space(s) between name and operator
|
||||
'(' + // Open operator + value group
|
||||
opRegex.source +
|
||||
'(?<space2>\\s*)' + // Optional space(s) between operator and value
|
||||
valueRegex.source +
|
||||
')?' + // Close operator + value group
|
||||
')' + // Close full set group
|
||||
'(?<space3>\\s*)$' // Optional space(s) at the end of the set
|
||||
);
|
||||
|
||||
const matched = textUntilCaret.match(fullRegex);
|
||||
|
||||
@@ -26,9 +26,7 @@ const intrinsics = ['duration', 'name', 'status', 'parent'];
|
||||
|
||||
const scopes: string[] = ['resource', 'span'];
|
||||
|
||||
const booleans = ['false', 'true'];
|
||||
|
||||
const keywords = intrinsics.concat(scopes).concat(booleans);
|
||||
const keywords = intrinsics.concat(scopes);
|
||||
|
||||
export const language = {
|
||||
ignoreCase: false,
|
||||
|
||||
@@ -95,10 +95,7 @@ export type Span = {
|
||||
kind?: SpanKind;
|
||||
startTimeUnixNano: string;
|
||||
endTimeUnixNano?: string;
|
||||
attributes?: Array<{
|
||||
key: string;
|
||||
value: { stringValue?: string; intValue?: string; boolValue?: boolean; doubleValue?: string };
|
||||
}>;
|
||||
attributes?: Array<{ key: string; value: { stringValue: string } }>;
|
||||
dropped_attributes_count?: number;
|
||||
};
|
||||
|
||||
|
||||
@@ -103,7 +103,6 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: ${theme.spacing(1)};
|
||||
`,
|
||||
alerts: css`
|
||||
margin: ${theme.spacing(0, 2, 0, 4)};
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
const fs = require('fs');
|
||||
|
||||
const cwd = process.cwd();
|
||||
const packageJson = require(`${cwd}/package.json`);
|
||||
|
||||
const newPackageJson = {
|
||||
...packageJson,
|
||||
main: packageJson.publishConfig?.main ?? packageJson.main,
|
||||
};
|
||||
|
||||
if (packageJson.publishConfig?.types) {
|
||||
newPackageJson.types = packageJson.publishConfig.types;
|
||||
}
|
||||
|
||||
if (packageJson.publishConfig?.module) {
|
||||
newPackageJson.module = packageJson.publishConfig.module;
|
||||
}
|
||||
|
||||
try {
|
||||
fs.writeFileSync(`${cwd}/package.json`, JSON.stringify(newPackageJson, null, 2));
|
||||
} catch (e) {}
|
||||
46
yarn.lock
46
yarn.lock
@@ -4275,9 +4275,9 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana-plugins/input-datasource@workspace:plugins-bundled/internal/input-datasource"
|
||||
dependencies:
|
||||
"@grafana/data": 9.3.0
|
||||
"@grafana/toolkit": 9.3.0
|
||||
"@grafana/ui": 9.3.0
|
||||
"@grafana/data": 9.3.0-beta.1
|
||||
"@grafana/toolkit": 9.3.0-beta.1
|
||||
"@grafana/ui": 9.3.0-beta.1
|
||||
"@types/jest": 26.0.15
|
||||
"@types/lodash": 4.14.149
|
||||
"@types/react": 17.0.30
|
||||
@@ -4298,12 +4298,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/data@9.3.0, @grafana/data@workspace:*, @grafana/data@workspace:packages/grafana-data":
|
||||
"@grafana/data@9.3.0-beta.1, @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.1
|
||||
"@grafana/schema": 9.3.0
|
||||
"@grafana/schema": 9.3.0-beta.1
|
||||
"@grafana/tsconfig": ^1.2.0-rc1
|
||||
"@rollup/plugin-commonjs": 23.0.2
|
||||
"@rollup/plugin-json": 5.0.1
|
||||
@@ -4362,7 +4362,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana/e2e-selectors@9.3.0, @grafana/e2e-selectors@workspace:*, @grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors":
|
||||
"@grafana/e2e-selectors@9.3.0-beta.1, @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:
|
||||
@@ -4388,7 +4388,7 @@ __metadata:
|
||||
"@babel/core": 7.19.6
|
||||
"@babel/preset-env": 7.19.4
|
||||
"@cypress/webpack-preprocessor": 5.15.2
|
||||
"@grafana/e2e-selectors": 9.3.0
|
||||
"@grafana/e2e-selectors": 9.3.0-beta.1
|
||||
"@grafana/tsconfig": ^1.2.0-rc1
|
||||
"@mochajs/json-file-reporter": ^1.2.0
|
||||
"@rollup/plugin-node-resolve": 15.0.1
|
||||
@@ -4507,15 +4507,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/runtime@9.3.0, @grafana/runtime@workspace:*, @grafana/runtime@workspace:packages/grafana-runtime":
|
||||
"@grafana/runtime@9.3.0-beta.1, @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.3.0
|
||||
"@grafana/e2e-selectors": 9.3.0
|
||||
"@grafana/data": 9.3.0-beta.1
|
||||
"@grafana/e2e-selectors": 9.3.0-beta.1
|
||||
"@grafana/faro-web-sdk": 1.0.0-beta2
|
||||
"@grafana/tsconfig": ^1.2.0-rc1
|
||||
"@grafana/ui": 9.3.0
|
||||
"@grafana/ui": 9.3.0-beta.1
|
||||
"@rollup/plugin-commonjs": 23.0.2
|
||||
"@rollup/plugin-node-resolve": 15.0.1
|
||||
"@sentry/browser": 6.19.7
|
||||
@@ -4552,7 +4552,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana/schema@9.3.0, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema":
|
||||
"@grafana/schema@9.3.0-beta.1, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana/schema@workspace:packages/grafana-schema"
|
||||
dependencies:
|
||||
@@ -4572,7 +4572,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana/toolkit@9.3.0, @grafana/toolkit@workspace:*, @grafana/toolkit@workspace:packages/grafana-toolkit":
|
||||
"@grafana/toolkit@9.3.0-beta.1, @grafana/toolkit@workspace:*, @grafana/toolkit@workspace:packages/grafana-toolkit":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana/toolkit@workspace:packages/grafana-toolkit"
|
||||
dependencies:
|
||||
@@ -4588,10 +4588,10 @@ __metadata:
|
||||
"@babel/preset-env": 7.18.9
|
||||
"@babel/preset-react": 7.18.6
|
||||
"@babel/preset-typescript": 7.18.6
|
||||
"@grafana/data": 9.3.0
|
||||
"@grafana/data": 9.3.0-beta.1
|
||||
"@grafana/eslint-config": 5.0.0
|
||||
"@grafana/tsconfig": ^1.2.0-rc1
|
||||
"@grafana/ui": 9.3.0
|
||||
"@grafana/ui": 9.3.0-beta.1
|
||||
"@jest/core": 27.5.1
|
||||
"@types/command-exists": ^1.2.0
|
||||
"@types/eslint": 8.4.1
|
||||
@@ -4672,16 +4672,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/ui@9.3.0, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui":
|
||||
"@grafana/ui@9.3.0-beta.1, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana/ui@workspace:packages/grafana-ui"
|
||||
dependencies:
|
||||
"@babel/core": 7.19.6
|
||||
"@emotion/css": 11.10.5
|
||||
"@emotion/react": 11.10.5
|
||||
"@grafana/data": 9.3.0
|
||||
"@grafana/e2e-selectors": 9.3.0
|
||||
"@grafana/schema": 9.3.0
|
||||
"@grafana/data": 9.3.0-beta.1
|
||||
"@grafana/e2e-selectors": 9.3.0-beta.1
|
||||
"@grafana/schema": 9.3.0-beta.1
|
||||
"@grafana/tsconfig": ^1.2.0-rc1
|
||||
"@leeoniya/ufuzzy": 0.8.0
|
||||
"@mdx-js/react": 1.6.22
|
||||
@@ -4956,11 +4956,11 @@ __metadata:
|
||||
resolution: "@jaegertracing/jaeger-ui-components@workspace:packages/jaeger-ui-components"
|
||||
dependencies:
|
||||
"@emotion/css": 11.10.5
|
||||
"@grafana/data": 9.3.0
|
||||
"@grafana/e2e-selectors": 9.3.0
|
||||
"@grafana/runtime": 9.3.0
|
||||
"@grafana/data": 9.3.0-beta.1
|
||||
"@grafana/e2e-selectors": 9.3.0-beta.1
|
||||
"@grafana/runtime": 9.3.0-beta.1
|
||||
"@grafana/tsconfig": ^1.2.0-rc1
|
||||
"@grafana/ui": 9.3.0
|
||||
"@grafana/ui": 9.3.0-beta.1
|
||||
"@testing-library/jest-dom": 5.16.5
|
||||
"@testing-library/react": 12.1.4
|
||||
"@testing-library/user-event": 14.4.3
|
||||
|
||||
Reference in New Issue
Block a user