From b8b7d7c2058f4aa88f63b35671d6f70664f54b08 Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 10:35:27 -0600 Subject: [PATCH 001/146] Release: update changelog for 10.4.17+security-01 (#104304) Update changelog Co-authored-by: github-actions[bot] --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe5892b4082..711dab275ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ + + +# 10.4.17+security-01 (2025-04-22) + +### Features and enhancements + +- **Chore:** Bump Go version to 1.23.7 [#101565](https://github.com/grafana/grafana/pull/101565), [@macabu](https://github.com/macabu) +- **Chore:** Bump Go version to 1.23.7 (Enterprise) +- **Chore:** Bump golang-jwt/jwt/v4 and golang-jwt/jwt/v5 to address security issues [#102762](https://github.com/grafana/grafana/pull/102762), [@macabu](https://github.com/macabu) + +### Bug fixes + +- **Alerting:** Update slack image upload to use new API [#101483](https://github.com/grafana/grafana/pull/101483), [@moustafab](https://github.com/moustafab) +- **Service Accounts:** Do not show error pop-ups for Service Account and Renderer UI flows [#101804](https://github.com/grafana/grafana/pull/101804), [@IevaVasiljeva](https://github.com/IevaVasiljeva) + + # 11.6.0 (2025-03-25) From e42cca9527ba67b8ae1c72490c479b1853043be5 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Tue, 22 Apr 2025 17:51:32 +0100 Subject: [PATCH 002/146] Chore: fix translations missing keys (#104314) fix translations missing keys --- .../components/expressions/Expression.tsx | 5 ++++- .../TracePageHeader/TracePageHeader.tsx | 2 +- .../features/expressions/components/Math.tsx | 2 +- .../app/features/users/TokenRevokedModal.tsx | 5 ++++- public/locales/en-US/grafana.json | 20 +++++++------------ 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/public/app/features/alerting/unified/components/expressions/Expression.tsx b/public/app/features/alerting/unified/components/expressions/Expression.tsx index 0c245131d38..27050ef7139 100644 --- a/public/app/features/alerting/unified/components/expressions/Expression.tsx +++ b/public/app/features/alerting/unified/components/expressions/Expression.tsx @@ -269,7 +269,10 @@ export const ExpressionResult: FC = ({ series, isAlertCon /> - + {'{{pageStart}}'} - {'{{pageEnd}}'} of {'{{numPages}}'} diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.tsx index b60fcb18039..79488bf9ce0 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.tsx @@ -116,7 +116,7 @@ export const TracePageHeader = memo((props: TracePageHeaderProps) => { <>
{
{ >

- + Your session token was automatically revoked because you have reached{' '} the maximum number of {'{{numSessions}}'} concurrent sessions for your account. diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 4722abd4b66..01564808222 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -1,7 +1,5 @@ { "_comment": "The code is the source of truth for English phrases. They should be updated in the components directly, and additional plurals specified in this file.", - "{{pageStart}} - {{pageEnd}} of {{numPages}}": "{{pageStart}} - {{pageEnd}} of {{numPages}}", - "{{url}} or {{target}} or {{path}}": "{{url}} or {{target}} or {{path}}", "access-control": { "add-permission": { "built-in-aria-label": "Built-in role picker", @@ -970,7 +968,8 @@ "expression-result": { "aria-label-nextpage": "next-page", "aria-label-previouspage": "previous-page", - "no-data": "No data" + "no-data": "No data", + "page-counter": "{{pageStart}} - {{pageEnd}} of {{numPages}}" }, "federated-rule-warning": { "experimental": "Federated rule groups are currently an experimental feature.", @@ -5001,7 +5000,8 @@ "title-share-thoughts-about-tracing-grafana": "Share your thoughts about tracing in Grafana." }, "trace-page-header": { - "text-partial-trace": "Partial trace" + "text-partial-trace": "Partial trace", + "tooltip-url": "{{url}} or {{target}} or {{path}}" }, "trace-page-search-bar": { "aria-label-clear-filters": "Clear filters button", @@ -5079,6 +5079,7 @@ }, "math": { "available-math-functions": "Available math functions", + "run-math-operations": "Run math operations on one or more queries. You reference the query by {{refExample}} ie. {{ref1}}, {{ref2}}, {{ref3}}etc.<10>Example: <12>{{example}}", "tooltip-footer": "See our additional documentation on <2>Math expressions.", "tooltip-title": "Math operator", "tooltip-trigger": "Expression" @@ -7895,13 +7896,6 @@ "reload-button": "Reload", "title": "Unable to find application file" }, - "Run math operations on one or more queries": { - " You reference the query by {{refExample}} ie": { - " {{ref1}}, {{ref2}}, {{ref3}}etc": { - "<10>Example: <12>{{example}}": "Run math operations on one or more queries. You reference the query by {{refExample}} ie. {{ref1}}, {{ref2}}, {{ref3}}etc.<10>Example: <12>{{example}}" - } - } - }, "sandbox": { "test-stuff-page": { "application-notifications-toasts-testing": "Application notifications (toasts) testing", @@ -8889,6 +8883,7 @@ "message": "No users found" }, "token-revoked-modal": { + "auto-revoked": "Your session token was automatically revoked because you have reached <2>the maximum number of {{numSessions}} concurrent sessions for your account.", "resume-message": "<0>To resume your session, sign in again.Contact your administrator or visit the license page to review your quota if you are repeatedly signed out automatically.", "sign-in": "Sign in", "title-you-have-been-automatically-signed-out": "You have been automatically signed out" @@ -9019,6 +9014,5 @@ "data-hover-view": { "link": "Link" } - }, - "Your session token was automatically revoked because you have reached <2>the maximum number of {{numSessions}} concurrent sessions for your account": "Your session token was automatically revoked because you have reached <2>the maximum number of {{numSessions}} concurrent sessions for your account." + } } From 576bf66e03016bdb81ef1b24dab8b80980d23049 Mon Sep 17 00:00:00 2001 From: Jack Baldry Date: Tue, 22 Apr 2025 17:53:41 +0100 Subject: [PATCH 003/146] Add Observability as Code documentation (#104301) Co-authored-by: Kim Nylander Co-authored-by: Kim Nylander <104772500+knylander-grafana@users.noreply.github.com> --- docs/sources/observability-as-code/_index.md | 79 ++++++ .../observability-as-code/get-started.md | 78 ++++++ .../provision-resources/_index.md | 77 ++++++ .../provision-resources/file-path-setup.md | 162 ++++++++++++ .../provision-resources/git-sync-setup.md | 238 ++++++++++++++++++ .../provision-resources/intro-git-sync.md | 88 +++++++ .../provisioned-dashboards.md | 137 ++++++++++ .../provision-resources/use-git-sync.md | 84 +++++++ 8 files changed, 943 insertions(+) create mode 100644 docs/sources/observability-as-code/_index.md create mode 100644 docs/sources/observability-as-code/get-started.md create mode 100644 docs/sources/observability-as-code/provision-resources/_index.md create mode 100644 docs/sources/observability-as-code/provision-resources/file-path-setup.md create mode 100644 docs/sources/observability-as-code/provision-resources/git-sync-setup.md create mode 100644 docs/sources/observability-as-code/provision-resources/intro-git-sync.md create mode 100644 docs/sources/observability-as-code/provision-resources/provisioned-dashboards.md create mode 100644 docs/sources/observability-as-code/provision-resources/use-git-sync.md diff --git a/docs/sources/observability-as-code/_index.md b/docs/sources/observability-as-code/_index.md new file mode 100644 index 00000000000..3f92ff84655 --- /dev/null +++ b/docs/sources/observability-as-code/_index.md @@ -0,0 +1,79 @@ +--- +_build: + list: false +noindex: true +cascade: + noindex: true +description: Overview of Observability as Code including description, key features, and explanation of benefits. +keywords: + - observability + - configuration + - as code + - dashboards + - git integration + - git sync + - github +labels: + products: + - enterprise + - oss +title: Observability as Code +weight: 100 +--- + +# Observability as Code + +Observability as Code lets you apply code management best practices to your observability resources. +Using Observability as Code, you can version, automate, and scale Grafana configurations, including dashboards and observability workflows. +By representing Grafana resources as code, you can integrate them into existing infrastructure-as-code workflows and apply standard development practices. + +Observability as Code provides more control over configuration. Instead of manually configuring dashboards or settings through the Grafana UI, you can: + +- **Write configurations in code:** Define dashboards in JSON or other supported formats. +- **Sync your Grafana setup to GitHub:** Track changes, collaborate, and roll back updates using Git and GitHub, or other remote sources. +- **Automate with CI/CD:** Integrate Grafana directly into your development and deployment pipelines. +- **Standardize workflows:** Ensure consistency across your teams by using repeatable, codified processes for managing Grafana resources. + +{{< section depth=5 >}} + + diff --git a/docs/sources/observability-as-code/get-started.md b/docs/sources/observability-as-code/get-started.md new file mode 100644 index 00000000000..6e7c53d772c --- /dev/null +++ b/docs/sources/observability-as-code/get-started.md @@ -0,0 +1,78 @@ +--- +description: Get started with Observability as Code by exploring the documentation, libraries, and tools available for as-code practices. +keywords: + - configuration + - as code + - as-code + - dashboards + - Git Sync + - Git +labels: + products: + - enterprise + - oss +title: Get started with Observability as Code +weight: 100 +--- + +# Get started with Observability as Code + +Simply put, with Observability as Code, you can manage Grafana resources. +You can write code that describes what you want the dashboard to do, rather than manipulate it via the UI. + +Observability as Code lets you manage dashboards, resources, and configurations programmatically, leveraging powerful tools for automation and standardization. + +## Get started with Observability as Code + + + +1. [**Set up Git Sync**](https://grafana.com/docs/grafana//observability-as-code/provision-resources/git-sync-setup/) + + - Configure Git repositories to store your dashboard JSON files. + - Understand best practices for version control, including collaboration through pull requests and rollbacks. + - Edit your JSON files in GitHub and then sync with Grafana. + +1. [**Manage dashboard deployments from GitHub**](https://grafana.com/docs/grafana//observability-as-code/provision-resources/use-git-sync/) + + - Integrate dashboards into CI/CD pipelines using tools like GitHub Actions. + - Leverage provisioning features in Grafana to automate updates and deployment of dashboards. + + +## Explore additional Observability as Code tools + +- [**Crossplane:**](https://github.com/grafana/crossplane-provider-grafana) Manage Grafana resources using Kubernetes manifests with the Grafana Crossplane provider. +- [**Grafonnet:**](https://github.com/grafana/grafonnet) Grafonnet is a Jsonnet library for generating Grafana dashboard JSON definitions programmatically. It is currently in the process of being deprecated. +- [**Grizzly:**](https://grafana.com/docs/grafana-cloud/developer-resources/infrastructure-as-code/grizzly/dashboards-folders-datasources/) Grizzly is a command-line tool that simplifies managing Grafana resources using Kubernetes-inspired YAML syntax. It is currently in the process of being deprecated. diff --git a/docs/sources/observability-as-code/provision-resources/_index.md b/docs/sources/observability-as-code/provision-resources/_index.md new file mode 100644 index 00000000000..65785d437df --- /dev/null +++ b/docs/sources/observability-as-code/provision-resources/_index.md @@ -0,0 +1,77 @@ +--- +description: Learn about how to provision resource using Git Sync and local file provisioning administration. +keywords: + - observability + - configuration + - as code + - git integration + - git sync + - github +labels: + products: + - enterprise + - oss +title: Provision resources and sync dashboards +weight: 100 +--- + +# Provision resources and sync dashboards + +{{< admonition type="caution" >}} +Provisioning is an [experimental feature](https://grafana.com/docs/release-life-cycle/) introduced in Grafana v12 for open source and Enterprise editions. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. This feature isn't available in Grafana Cloud. +{{< /admonition >}} + +{{< section depth="5" >}} + +


+ +Using Provisioning, you can configure how to store your dashboard JSON files in either GitHub repositories using Git Sync or a local path. + +Of the two experimental options, Git Sync is the recommended method for provisioning your dashboards. You can synchronize any new dashboards and changes to existing dashboards to your configured GitHub repository. +If you push a change in the repository, those changes are mirrored in your Grafana instance. +For more information on configuring Git Sync, refer to [Set up Git Sync](https://grafana.com/docs/grafana//observability-as-code/provision-resources/git-sync-setup). + +Refer to [Set up file provisioning](https://grafana.com/docs/grafana//observability-as-code/provision-resources/file-path-setup/) to learn more about the version of local file provisioning in Grafana 12. + +## Provisioned folders and connections + +Dashboards and folders saved to the local path are referred to as "provisioned" resources and are labeled as such in the Grafana UI. + +Dashboards saved in your GitHub repository or local folder configured appear in a provisioned folder in Grafana. + +You can set a single folder, or multiple folders to a different repository, with up to 10 connections. Alternatively, your entire Grafana instance can be the provisioned folder. + +## How it works + +A user decides to update a provisioned dashboard that is either stored within a GitHub repository (Git Sync workflow) or in a local file (local file workflow). + +### Git Sync workflow + +Resources provisioned with Git Sync can be modified from within the Grafana UI or within the GitHub repository. +Changes made in either the repository or the Grafana UI are bidirectional. + +For example, when a user updates dashboards within the Grafana UI, they choose **Save** to preserve the changes. +Grafana notifies them that the dashboard is provisioned in a GitHub repository. +They choose how to preserve their changes: either saved directly to a branch or pushed to a new branch using a pull request in GitHub. +If they chose a new branch, then they open the pull request and follow their normal workflow. + +Grafana polls GitHub at a regular interval. +The connection is established using a personal access token for authorization. +With the webhooks feature enabled, repository notifications appear almost immediately. +Without webhooks, Grafana polls for changes at the specified interval. +The default polling interval is 60 seconds. + +Any changes made in the provisioned files stored in the GitHub repository are reflected in the Grafana database. +The Grafana UI reads the database and updates the UI to reflect these changes. + +### Local file workflow + +In the local file workflow, all provisioned resources are changed in the local files. +The user can't use the Grafana UI to edit or delete provisioned resources. + +Any changes made in the provisioned files are reflected in the Grafana database. +The Grafana UI reads the database and updates the UI to reflect these changes. + +## Explore provisioning + +{{< section withDescriptions="true" depth="5" >}} diff --git a/docs/sources/observability-as-code/provision-resources/file-path-setup.md b/docs/sources/observability-as-code/provision-resources/file-path-setup.md new file mode 100644 index 00000000000..9c8e8de5896 --- /dev/null +++ b/docs/sources/observability-as-code/provision-resources/file-path-setup.md @@ -0,0 +1,162 @@ +--- +description: Instructions for setting up file provisioning with a local path. +keywords: + - as code + - as-code + - file provisioning + - local path +labels: + products: + - enterprise + - oss +title: Set up file provisioning +weight: 200 +--- + +# Set up file provisioning + +{{< admonition type="note" >}} +Local file provisioning is an [experimental feature](https://grafana.com/docs/release-life-cycle/) introduced in Grafana v12 for open source and Enterprise editions. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. Enable the `provisioning` and `kubernetesDashboards` feature toggles in Grafana to use this feature. This feature isn't available in Grafana Cloud. +{{< /admonition >}} + +- [Provision resources and sync dashboards](/docs/grafana//observability-as-code/provision-resources/) + - [Git Sync](/docs/grafana//observability-as-code/provision-resources/intro-git-sync/) + - [Set up Git Sync](/docs/grafana//observability-as-code/provision-resources/git-sync-setup/) + - [Set up file provisioning](/docs/grafana//observability-as-code/provision-resources/file-path-setup/) + - [Work with provisioned dashboards](/docs/grafana//observability-as-code/provision-resources/provisioned-dashboards/) + - [Manage provisioned repositories with Git Sync](/docs/grafana/observability-as-code/provision-resources/use-git-sync/) + +
+ +File provisioning in Grafana lets you include resources, including folders and dashboard JSON files, that are stored in a local file system. + +This page explains how to set up local file provisioning. + +The local path mount is referred to as a repository. + +Using the local path lets you also use it with a tool like `fuse`, allowing you to mount S3 buckets as local paths. You can also use tools like `restic` to automatically back up your dashboards to your preferred backup storage solution. + +To set up file sync with local with local files, you need to: + +1. Enable feature toggles and paths in Grafana configuration file (first time set up). +1. Set the local path. +1. Choose what content to sync with Grafana. + +## New file provisioning capabilities + +Local file provisioning using **Administration** > **Provisioning** will eventually replace the traditional methods Grafana has used for referencing local file systems for dashboard files. + +{{< admonition type="note" >}} +For production system, we recommend using the `folderFromFilesStructure` capability instead of **Administration** > **Provisioning** to include dashboards from a local file system in your Grafana instance. +Refer to [Provision Grafana](https://grafana.com/docs/grafana//administration/provisioning/#provision-folders-structure-from-filesystem-to-grafana) for more information. +{{< /admonition >}} + +### Limitations + +- A provisioned dashboard can't be deleted from within Grafana UI. The dashboard has to be deleted at the local file system and those changes synced to Grafana. +- Changes from the local file system are one way: you can't save changes from + +## Before you begin + +To set up file provisioning, you need: + +- Administration rights in your Grafana organization. +- A local directory where your dashboards will be stored. + - If you want to use a GitHub repository, refer to [Set up Git Sync](https://grafana.com/docs/grafana//observability-as-code/provision-resources/file-path-setup/). +- To update the `permitted_provisioning_paths` section of `custom.ini`. +- To enable the required feature toggles in your Grafana instance. + +## Enable required feature toggles and configure permitted paths + +To activate local file provisioning in Grafana, you need to enable the `provisioning` and `kubernetesDashboards` feature toggles. +For additional information about feature toggles, refer to [Configure feature toggles](https://grafana.com/docs/grafana//setup-grafana/configure-grafana/feature-toggles). + +The local setting must be a relative path and its relative path must be configured in the `permitted_provisioned_paths` configuration option. +The configuration option is relative to your working directory, i.e. where you are running Grafana from; this is usually `/usr/share/grafana` or similar. + +Local file paths can point to any directory that is permitted by the configuration. +The default paths is `devenv/dev-dashboards` and `conf/provisioning` in your `grafana` installation directory. + +The path must behave as a standard file directory on the system of choice. +Any subdirectories are automatically included. + +The values that you enter for the `permitted_provisioning_paths` become the base paths for those entered when you enter a local path in the **Connect to local storage** wizard. + +1. Open your Grafana configuration file, either `grafana.ini` or `custom.ini`. For file location based on operating system, refer to [Configuration file location](https://grafana.com/docs/grafana//setup-grafana/configure-grafana/feature-toggles/#experimental-feature-toggles). +1. Locate or add a `[feature_toggles]` section. Add these values: + + ```ini + [feature_toggles] + provisioning = true + kubernetesDashboards = true ; use k8s from browser + + # If you want easy kubectl setup development mode + grafanaAPIServerEnsureKubectlAccess = true + ``` + +1. Locate or add a `[paths]` section. To add more than one location, use the pipe character (`|`) to separate the paths. The list should not include empty paths or trailing pipes. Add these values: + + ```ini + [paths] + ; This is devenv/dev-dashboards and conf/provisioning by default. + permitted_provisioning_paths = grafana/ | /etc/grafana/provisioning/ + ``` + +1. Save the changes to the file and start Grafana. + +## Set up file-based provisioning + +To use file-based provisioning, you need the file path to the `grafana` folder where your dashboards are stored in the repository. + +To start setting up file-based provisioning: + +1. Log in to your Grafana server with an account that has the Grafana Admin flag set. +1. Select **Administration** in the left-side menu and then **Provisioning**. +1. Select [Configure file provisioning](#set-up-file-based-provisioning). + +### Connect to local storage + +The local path can point to any directory that is permitted by the configuration. +Refer to [Enabled required feature toggles and paths](#enable-required-feature-toggles-and-configure-permitted-paths) for information. + +The starting path is always your working `grafana` directory. +The prefix that must be entered is determined by the locations configured in `permitted_provisioning_paths`. +The default paths are `devenv/dev-dashboards` and `conf/provisioning` in your `grafana` installation directory. +The value you enter in the Grafana UI must _begin_ with any of the configured values. For example, `conf/provisioning/test` is valid, but `conf/test` is not. + +1. Enter the **Local path**, for example `grafana/`. This must begin with any of the configured `permitted_provisioned_paths`. +1. Select **Choose what to synchronize**. + +The set up process verifies the path and provides an error message if a problem occurs. + +### Choose what to synchronize + +In this section, you determine the actions taken with the storage you selected. + +1. Select how resources should be handled in Grafana. + +- Choose **Sync all resources with external storage** if you want to sync and manage your entire Grafana instance through external storage. You can only have one provisioned connection with this selection. +- Choose **Sync external storage to new Grafana folder** to sync external resources into a new folder without affecting the rest of your instance. You can repeat this process for up to 10 folders. - Enter a **Display name** for the repository connection. Resources stored in this connection appear under the chosen display name in the Grafana UI. + + +1. Select **Synchronize** to continue. + +### Synchronize with external storage + +After this one time step, all future updates are automatically saved to the local file path and provisioned back to the instance. + +During the initial synchronization, your dashboards will be temporarily unavailable. No data or configurations will be lost. +How long the process takes depends upon the number of resources involved. + +Select **Begin synchronization** to start the process. + +### Choose additional settings + +If you wish, you can make any files synchronized as as **Read only** so no changes can be made to the resources through Grafana. +Any resources made outside of Grafana and saved to the local repository will be reflected in Grafana. + +Select **Finish**. + +## Verify your dashboards in Grafana + +To verify that your dashboards are available at the location that you specified, click **Dashboards**. The name of the dashboard is listed in the **Name** column. diff --git a/docs/sources/observability-as-code/provision-resources/git-sync-setup.md b/docs/sources/observability-as-code/provision-resources/git-sync-setup.md new file mode 100644 index 00000000000..31ae8a951a1 --- /dev/null +++ b/docs/sources/observability-as-code/provision-resources/git-sync-setup.md @@ -0,0 +1,238 @@ +--- +description: Instructions for setting up Git Sync, so you can provision GitHub repositories for use with Grafana. +keywords: + - set up + - git integration + - git sync + - github +labels: + products: + - enterprise + - oss +title: Set up Git Sync +weight: 100 +--- + +# Set up Git Sync + +{{< admonition type="note" >}} +Git Sync is an [experimental feature](https://grafana.com/docs/release-life-cycle/) introduced in Grafana v12 for open source and Enterprise editions. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. Enable the `provisioning` and `kubernetesDashboards` feature toggles in Grafana to use this feature. Git Sync isn't available in Grafana Cloud. +{{< /admonition >}} + +- [Provision resources and sync dashboards](/docs/grafana//observability-as-code/provision-resources/) + - [Git Sync](/docs/grafana//observability-as-code/provision-resources/intro-git-sync/) + - [Set up Git Sync](/docs/grafana//observability-as-code/provision-resources/git-sync-setup/) + - [Set up file provisioning](/docs/grafana//observability-as-code/provision-resources/file-path-setup/) + - [Work with provisioned dashboards](/docs/grafana//observability-as-code/provision-resources/provisioned-dashboards/) + - [Manage provisioned repositories with Git Sync](/docs/grafana/observability-as-code/provision-resources/use-git-sync/) + +
+ +Git Sync lets you manage Grafana dashboards as code by storing dashboards JSON files and folders in a remote GitHub repository. +Alternatively, you can configure a local file system instead of using GitHub. +Refer to [Set up file provisioning](https://grafana.com/docs/grafana//observability-as-code/provision-resources/file-path-setup/) for information. + +This page explains how to use Git Sync with a GitHub repository. + +To set up Git Sync, you need to: + +1. Enable feature toggles in Grafana (first time set up). +1. Configure a connection to your GitHub repository. +1. Choose what content to sync with Grafana. +1. Optional: Extend Git Sync by enabling pull request notifications and image previews of dashboard changes. + +| Capability | Benefit | Requires | +| ----------------------------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------- | +| Adds a table summarizing changes to your pull request | Provides a convenient way to save changes back to GitHub. | Webhooks configured | +| Add a dashboard preview image to a PR | View a snapshot of dashboard changes to a pull request without opening Grafana. | Image renderer plugin and webhooks configured | + +## Performance impacts of enabling Git Sync + +Git Sync is an experimental feature and is under continuous development. + +We recommend evaluating the performance impact, if any, in a non-production environment. + +When Git Sync is enabled, the database load might increase, especially for instances with a lot of folders and nested folders. +Reporting any issues you encounter can help us improve Git Sync. + +## Before you begin + +To set up Git Sync, you need: + +- Administration rights in your Grafana organization. +- Enable the required feature toggles in your Grafana instance. Refer to [Enable required feature toggles](#enable-required-feature-toggles) for instructions. +- A GitHub repository to store your dashboards in. + - If you want to use a local file path, refer to [the local file path guide](https://grafana.com/docs/grafana//observability-as-code/provision-resources/file-path-setup/). +- A GitHub access token. The Grafana UI will also explain this to you as you set it up. +- Optional: A public Grafana instance. +- Optional: Image Renderer plugin to save image previews with your PRs. + +## Enable required feature toggles + +To activate Git Sync in Grafana, you need to enable the `provisioning` and `kubernetesDashboards` feature toggles. +For additional information about feature toggles, refer to [Configure feature toggles](https://grafana.com/docs/grafana//setup-grafana/configure-grafana/feature-toggles). + +To enable the required feature toggles, add them to your Grafana configuration file: + +1. Open your Grafana configuration file, either `grafana.ini` or `custom.ini`. For file location based on operating system, refer to [Configuration file location](https://grafana.com/docs/grafana//setup-grafana/configure-grafana/feature-toggles/#experimental-feature-toggles). +1. Locate or add a `[feature_toggles]` section. Add these values: + + ```ini + [feature_toggles] + provisioning = true + kubernetesDashboards = true ; use k8s from browser + + # If you want easy kubectl setup development mode + grafanaAPIServerEnsureKubectlAccess = true + ``` + +1. Save the changes to the file and restart Grafana. + +## Create a GitHub access token + +Whenever you connect to a GitHub repository, you need to create a GitHub access token with specific repository permissions. +This token needs to be added to your Git Sync configuration to enable read and write permissions between Grafana and GitHub repository. + +1. Create a new token using [Create new fine-grained personal access token](https://github.com/settings/personal-access-tokens/new). Refer to [Managing your personal access tokens](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) for instructions. +1. Under **Permissions**, expand **Repository permissions**. +1. Set these permissions for Git Sync: + + - **Contents**: Read and write permission + - **Metadata**: Read-only permission + - **Pull requests**: Read and write permission + - **Webhooks**: Read and write permission + +1. Select any additional options and then press **Generate token**. +1. Verify the options and select **Generate token**. +1. Copy the access token. Leave the browser window available with the token until you've completed configuration. + +GitHub Apps are not currently supported. + +## Set up the connection to GitHub + +Use **Provisioning** to guide you through setting up Git Sync to use a GitHub repository. + +1. Log in to your Grafana server with an account that has the Grafana Admin flag set. +1. Select **Administration** in the left-side menu and then **Provisioning**. +1. Select **Configure Git Sync**. + +### Connect to external storage + +To connect your GitHub repository, follow these steps: + +1. Paste your GitHub personal access token into **Enter your access token**. Refer to [Create a GitHub access token](#create-a-github-access-token) for instructions. +1. Paste the **Repository URL** for your GitHub repository into the text box. +1. Enter a branch to use. The default value is `main`. +1. Add a **Path** to a subdirectory where your dashboards are stored. The default value is `grafana/`. If your dashboards are stored in the root of your repository, then remove the directory name. +1. Select **Choose what to synchronize** to have the connection to your repository verified and continue setup. + +### Choose what to synchronize + +You can choose to either use one repository for an entire organization or to a new Grafana folder (up to 10 connections). +If you choose to sync all resources with external storage, then all of your dashboards are synced to that one repository. +You won't have the option of setting up additional repositories to connect to. + +You can choose to synchronize all resources with GitHub or you can sync resources to a new Grafana folder. +The options you have depend on the status of your GitHub repository. +For example, if you are syncing with a new or empty repository, you won't have an option to migrate dashboards. + +1. Select how resources should be handled in Grafana. + +- Choose **Sync all resources with external storage** if you want to sync and manage your entire Grafana instance through external storage. You can only have one provisioned connection with this selection. +- Choose **Sync external storage to new Grafana folder** to sync external resources into a new folder without affecting the rest of your instance. You can repeat this process for up to 10 connections. - Enter a **Display name** for the repository connection. Resources stored in this connection appear under the chosen display name in the Grafana UI. + + +1. Select **Synchronize** to continue. + + + + +### Choose additional settings + +Finally, you can set up how often your configured storage is polled for updates. + +1. For **Update instance interval (seconds)**, enter how often you want the instance to pull updates from GitHub. The default value is 60 seconds. +1. Optional: Select **Read only** to ensure resources can't be modified in Grafana. + +1. Optional: If you have the Grafana Image Renderer plugin configured, you can **Enable dashboards previews in pull requests**. If image rendering is not available, then you can't select this option. For more information, refer to [Grafana Image Renderer](https://grafana.com/grafana/plugins/grafana-image-renderer/). +1. Select **Finish** to proceed. + +## Verify your dashboards in Grafana + +To verify that your dashboards are available at the location that you specified, click **Dashboards**. The name of the dashboard is listed in the **Name** column. + +Now that your dashboards have been synced from a repository, you can customize the name, change the branch, and create a pull request (PR) for it. +Refer to [Use Git Sync](https://grafana.com/docs/grafana//observability-as-code/provision-resources/use-git-sync/) for more information. + +## Configure webhooks and image rendering + +You can extend Git Sync by getting instant updates and pull requests using webhooks and add dashboard previews in pull requests. + +### Set up webhooks for realtime notification and pull request integration + +When connecting to a GitHub repository, Git Sync use webhooks to enable real-time updates from GitHub public repositories or enable the pull request integration. +Without webhooks, the polling interval is set in the final configuration screen (default is 60 seconds). +Your Grafana instance must be exposed to the public internet. +You can do this via port forwarding and DNS, a tool such as `ngrok`, or any other method you prefer. + +The permissions set in your GitHub access token provide the authorization for this communication. + +If you use local storage, then Git Sync only provides periodic pulling. + + + +Set up webhooks with whichever service or tooling you prefer. +For example, you can use Cloudflare Tunnels with a Cloudflare-managed domain, port-forwarding and DNS options, or a tool such as `ngrok`. + +After you have the public URL, you can add it to your Grafana configuration file: + +```yaml +[server] +root_url = https://PUBLIC_DOMAIN.HERE +``` + +You can check the configured webhooks in the **View** link for your GitHub repository from **Administration** > **Provisioning**. + +#### Necessary paths + +If your security setup does not permit publicly exposing the Grafana instance, you can either choose to allowlist the GitHub IP addresses, or expose only the necessary paths. + +The necessary paths required to be exposed are (RegExp): + +- `/apis/provisioning\.grafana\.app/v0(alpha1)?/namespaces/[^/]+/repositories/[^/]+/(webhook|render/.*)$` + + +### Set up image rendering for dashboard previews + +By setting up image rendering, you can add visual previews of dashboard updates directly in pull requests. +Image rendering also requires webhooks. + +You can enable this capability by installing the Grafana Image Renderer plugin in your Grafana instance. +For more information and installation instructions, refer to [Grafana Image Renderer](https://grafana.com/grafana/plugins/grafana-image-renderer/). + +## Modify configurations after set up is complete + +To update your repository configuration after you've completed set up: + +1. Log in to your Grafana server with an account that has the Grafana Admin flag set. +1. Select **Administration** in the left-side menu and then **Provisioning**. +1. Select **Settings** for the repository you wish to modify. +1. Use the **Configure repository** screen to update any of the settings. +1. Select **Save** to preserve the updates. diff --git a/docs/sources/observability-as-code/provision-resources/intro-git-sync.md b/docs/sources/observability-as-code/provision-resources/intro-git-sync.md new file mode 100644 index 00000000000..59dbf53695d --- /dev/null +++ b/docs/sources/observability-as-code/provision-resources/intro-git-sync.md @@ -0,0 +1,88 @@ +--- +description: Learn about Git Sync, the Grafana feature for storing and managing dashboards within GitHub repositories. +keywords: + - dashboards + - git integration + - git sync + - github +labels: + products: + - enterprise + - oss +title: Git Sync +weight: 100 +--- + +# Git Sync + +{{< admonition type="caution" >}} +Git Sync is an [experimental feature](https://grafana.com/docs/release-life-cycle/) introduced in Grafana v12 for open source and Enterprise editions. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. Enable the `provisioning` and `kubernetesDashboards` feature toggles in Grafana to use this feature. Git Sync isn't available in Grafana Cloud. +{{< /admonition >}} + +- [Provision resources and sync dashboards](/docs/grafana//observability-as-code/provision-resources/) + - [Git Sync](/docs/grafana//observability-as-code/provision-resources/intro-git-sync/) + - [Set up Git Sync](/docs/grafana//observability-as-code/provision-resources/git-sync-setup/) + - [Set up file provisioning](/docs/grafana//observability-as-code/provision-resources/file-path-setup/) + - [Work with provisioned dashboards](/docs/grafana//observability-as-code/provision-resources/provisioned-dashboards/) + - [Manage provisioned repositories with Git Sync](/docs/grafana/observability-as-code/provision-resources/use-git-sync/) + +
+ +Using Git Sync, you can: + +- Introduce a review process for creating and modifying dashboards +- Manage dashboard configuration outside of Grafana instances +- Replicate dashboards across multiple instances + +Whenever a dashboard is modified, Grafana can commit changes to Git upon saving. Users can configure settings to either enforce PR approvals before merging or allow direct commits. + +Users can push changes directly to GitHub and see them in Grafana. Similarly, automated workflows can do changes that will be automatically represented in Grafana by updating Git. + +Because the dashboards are defined in JSON files, you can enable as-code workflows where the JSON is output from Go, TypeScript, or another coding language in the format of a dashboard schema. + +To learn more about creating dashboards in a coding language to provision them for Git Sync, refer to the [Foundation SDK](https://grafana.com/docs/grafana//observability-as-code/foundation-sdk) documentation. + +## How it works + +Git Sync is bidirectional and also works with changes done directly in GitHub as well as within the Grafana UI. +Grafana periodically polls GitHub at a regular internal to synchronize any changes. +With the webhooks feature enabled, repository notifications appear almost immediately. +Without webhooks, Grafana polls for changes at the specified interval. +The default polling interval is 60 seconds. + +Any changes made in the provisioned files stored in the GitHub repository are reflected in the Grafana database. +The Grafana UI reads the database and updates the UI to reflect these changes. + +## Common use cases + +Git Sync in Grafana lets you manage dashboards as code. +Because your dashboard JSON files are stored in GitHub, you and your team can version control, collaborate, and automate deployments efficiently. + +### Version control and auditing + +Organizations can maintain a structured, version-controlled history of Grafana dashboards. +The version control lets you revert to previous versions when necessary, compare modifications across commits, and ensure transparency in dashboard management. +Additionally, having a detailed history of changes enhances compliance efforts, as teams can generate audit logs that document who made changes, when they were made, and why. + +### Automated deployment and CI/CD integration + +Teams can streamline their workflow by integrating dashboard updates into their CI/CD pipelines. +By pushing changes to GitHub, automated processes can trigger validation checks, test dashboard configurations, and deploy updates programmatically using the `grafanactl` CLI and Foundation SDK. +This reduces the risk of human errors, ensures consistency across environments, and enables a faster, more reliable release cycle for dashboards used in production monitoring and analytics. + +### Collaborative dashboard development + +With Git Sync, multiple users can work on dashboards simultaneously without overwriting each other’s modifications. +By leveraging pull requests and branch-based workflows, teams can submit changes for review before merging them into the main branch. This process not only improves quality control but also ensures that dashboards adhere to best practices and organizational standards. Additionally, GitHub’s built-in discussion and review tools facilitate effective collaboration, making it easier to address feedback before changes go live. + +### Multi-environment synchronization + +Enterprises managing multiple Grafana instances, such as development, staging, and production environments, can seamlessly sync dashboards across these instances. +This ensures consistency in visualization and monitoring configurations, reducing discrepancies that might arise from manually managing dashboards in different environments. +By using Git Sync, teams can automate deployments across environments, eliminating repetitive setup tasks and maintaining a standardized monitoring infrastructure across the organization. + +### Disaster recovery and backup + +By continuously syncing dashboards to GitHub, organizations can create an always-updated backup, ensuring dashboards are never lost due to accidental deletion or system failures. +If an issue arises--such as a corrupted dashboard, unintended modification, or a system crash--teams can quickly restore the latest functional version from the Git repository. +This not only minimizes downtime but also adds a layer of resilience to Grafana monitoring setups, ensuring critical dashboards remain available when needed. diff --git a/docs/sources/observability-as-code/provision-resources/provisioned-dashboards.md b/docs/sources/observability-as-code/provision-resources/provisioned-dashboards.md new file mode 100644 index 00000000000..4b03d96ee61 --- /dev/null +++ b/docs/sources/observability-as-code/provision-resources/provisioned-dashboards.md @@ -0,0 +1,137 @@ +--- +description: Update, save, and modify provisioned resources in Grafana using Git Sync. +keywords: + - dashboards + - provisioned files + - git sync + - github +labels: + products: + - enterprise + - oss +title: Work with provisioned dashboards +weight: 300 +--- + +# Work with provisioned dashboards + +{{< admonition type="note" >}} +Git Sync and File path provisioning an [experimental feature](https://grafana.com/docs/release-life-cycle/) introduced in Grafana v12 for open source and Enterprise editions. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. Enable the `provisioning` and `kubernetesDashboards` feature toggles in Grafana. These features aren't available in Grafana Cloud. +{{< /admonition >}} + +- [Provision resources and sync dashboards](/docs/grafana//observability-as-code/provision-resources/) + - [Git Sync](/docs/grafana//observability-as-code/provision-resources/intro-git-sync/) + - [Set up Git Sync](/docs/grafana//observability-as-code/provision-resources/git-sync-setup/) + - [Set up file provisioning](/docs/grafana//observability-as-code/provision-resources/file-path-setup/) + - [Work with provisioned dashboards](/docs/grafana//observability-as-code/provision-resources/provisioned-dashboards/) + - [Manage provisioned repositories with Git Sync](/docs/grafana/observability-as-code/provision-resources/use-git-sync/) + +
+ +Using Provisioning, you can choose to store your dashboard JSON files in either GitHub repositories using Git Sync or a local file path. + +For more information, refer to the [Dashboards](https://grafana.com/docs/grafana//dashboards/) documentation. + +## Provisioning methods + +Dashboards and folders synchronized using Git Sync or a local file path are referred to as "provisioned" resources. + +Of the two experimental options, Git Sync is the recommended method for provisioning your dashboards. +You can synchronize any new dashboards and changes to existing dashboards to your configured GitHub repository. +If you push a change in the repository, those changes are mirrored in your Grafana instance. +For more information on configuring Git Sync, refer to [Set up Git Sync](https://grafana.com/docs/grafana//observability-as-code/provision-resources/intro-git-sync/). + +### Local path provisioning + +Using the local path provisioning makes files from a specified path available within Grafana. +These provisioned resources can only be modified in the local files and not within Grafana. +Any changes made in the configured local path are updated in Grafana. + +Refer to [Set up file provisioning](https://grafana.com/docs/grafana//observability-as-code/provision-resources/file-path-setup) to learn more about the version of local file provisioning in Grafana 12. + +{{< admonition type="note" >}} +The experimental local path provisioning using **Administration** > **Provisioning** will replace the file provisioning methods Grafana uses for referencing local file. + +For production systems, use the established methods for provisioning file systems in Grafana. +Refer to [Provision Grafana](https://grafana.com/docs/grafana//administration/provisioning/#provision-folders-structure-from-filesystem-to-grafana) for more information. +{{< /admonition >}} + +## Manage dashboards provisioned with Git Sync + +Using Git Sync, you can manage your dashboards in the UI and synchronize them with a GitHub repository. + +Git Sync changes the behavior in Grafana for dashboards that are saved in Git Sync: + +- Dashboards saved in your repository or local folder configured with Git Sync appear in a provisioned folder in Grafana. +- Any dashboard folders saved with Git Sync have a **Provisioned** label in the UI. +- Any changes to a provisioned resources have to be saved to the repository by opening a pull request or committing directly to the `main` branch. + +You can set a single folder, or multiple folders to a different repository, with up to 10 connections. + +### Git workflow with dashboards + +By default, Git version control uses a branch-based workflow for changes. This means that you can: + +- Commit changes to an existing branch (such as `main`) or save them to a new branch in your GitHub repository. +- Use pull requests to review changes to dashboards. +- Preview the changes before merging. + +To learn more about Git, refer to [Getting Started - About Version Control](https://git-scm.com/book/en/v2/Getting-Started-About-Version-Control) of the [Pro Git book](https://git-scm.com/book/en/v2) in the official Git documentation. + +### Add and save a new dashboard + +When you create a new dashboard in a provisioned folder associated with a GitHub repository, you follow the same process you use for any new dashboard. +Refer to [Create a dashboard](http://grafana.com/docs/grafana//dashboards/build-dashboards/create-dashboard/) for more information. + +After you create the dashboard, the steps are similar to [Save dashboard changes to GitHub](#save-dashboard-changes-to-github). + +1. Select **Save** to preserve the new dashboard. +1. Enter a title for the dashboard and a description. +1. Select the provisioned folder from the **Folder** drop-down list. +1. In **Path**, provide the path for your repository, ending in a JSON or YAML file. +1. For **Workflow**, select **Push to main** to make a Git commit directly to the repository or **Push to a new branch** to create a pull request. + - **Branch**: Specify the branch name in GitHub (for example, main). This option only appears if you select **Push to a new branch**. +1. Select **Save**. + +### Save dashboard changes to GitHub + +When you edit a provisioned resource, you are prompted to save or discard those changes. +Saving changes requires opening a pull request in your GitHub repository. + +1. Select **Edit** to update a provisioned dashboard. Make your desired changes. + +1. Click **Save dashboard**. + +1. On the **Provisioned dashboard** panel, choose the options you want to use: + + - **Update default refresh value**: Check this box to make the current refresh the new default. + - **Update default variable values**: Check this box to make the current values the new default. + - **Path**: Provide the path for your repository, ending in a JSON or YAML file. + - **Workflow:** Select **Push to main** to make a Git commit directly to the repository or **Push to a new branch** to create a pull request. + - **Branch**: Specify the branch name in GitHub (for example, main). This option only appears if you select **Push to a new branch**. + - **Comment**: Add a comment describing your changes. + +1. Optional: Select the **Changes** tab to view the differences between the updates you made and the original resource. + +1. Select **Save**. + +1. If you chose **Push to a new branch**, select **Open a pull request in GitHub** to open a new PR to your repository. GitHub opens with your dashboard’s code as the contents of the PR. + +1. Follow your usual GitHub workflow to save and merge the PR to your repository. + +### Tips + +- Use GitHub pull requests for changes to maintain review processes. +- Provide clear commit messages describing your changes. +- Regularly sync your repository to keep Grafana up to date. +- Review the **Events** tab to monitor sync status. + +## Manage dashboards provisioned with file provisioning + +To update any resources in the local path, you need to edit the files directly and then save them locally. +These changes are synchronized to Grafana. +However, you can't create, edit, or delete these resources using the Grafana UI. + +For more information, refer to [How it works](https://grafana.com/docs/grafana//observability-as-code/provision-resources/). + +Refer to [Set up file provisioning](https://grafana.com/docs/grafana//observability-as-code/provision-resources/file-path-setup/) for configuration instructions. diff --git a/docs/sources/observability-as-code/provision-resources/use-git-sync.md b/docs/sources/observability-as-code/provision-resources/use-git-sync.md new file mode 100644 index 00000000000..192a73226e1 --- /dev/null +++ b/docs/sources/observability-as-code/provision-resources/use-git-sync.md @@ -0,0 +1,84 @@ +--- +description: Instructions for working with Git Sync to perform common tasks, such as saving dashboards to GitHub and synchronizing changes with Grafana. +keywords: + - as code + - as-code + - dashboards + - git integration + - git sync + - github +labels: + products: + - enterprise + - oss +title: Manage provisioned repositories with Git Sync +menuTitle: Manage repositories +weight: 400 +--- + +# Manage provisioned repositories with Git Sync + +- [Provision resources and sync dashboards](/docs/grafana//observability-as-code/provision-resources/) + - [Git Sync](/docs/grafana//observability-as-code/provision-resources/intro-git-sync/) + - [Set up Git Sync](/docs/grafana//observability-as-code/provision-resources/git-sync-setup/) + - [Set up file provisioning](/docs/grafana//observability-as-code/provision-resources/file-path-setup/) + - [Work with provisioned dashboards](/docs/grafana//observability-as-code/provision-resources/provisioned-dashboards/) + - [Manage provisioned repositories with Git Sync](/docs/grafana/observability-as-code/provision-resources/use-git-sync/) + +
+ +After you have set up Git Sync, you can synchronize dashboards and changes to existing dashboards to your configured GitHub repository. +If you push a change in the repository, those changes are mirrored in your Grafana instance. + +## View current status of synchronization + +Each repository synchronized with Git Sync has a dashboard that provides a summary of resources, health, pull status, webhook, sync jobs, resources, and files. +Use the detailed information accessed in **View** to help troubleshoot and understand the health of your repository's connection with Grafana. + +To view the current status, follow these steps. + +1. Log in to your Grafana server with an account that has the Grafana Admin or Editor flag set. +1. Select **Administration** in the left-side menu and then **Provisioning**. +1. Locate the repository you are interested in. +1. If you see a green `Up-to-date` label next to the repository name, then everything is syncing as expected. +1. Select **View** to access detailed dashboards and reports about the synchronization history of your repository. + +## Synchronize changes + +Synchronizing resources from provisioned repositories into your Grafana instance pulls the resources into the selected folder. Existing dashboards with the same `uid` are overwritten. + +To sync changes from your dashboards with your Git repository: + +1. From the left menu, select **Administration** > **Provisioning**. +1. Select **Pull** under the repository you want to sync. +1. Wait for the synchronization process to complete. + +## Remove a repository + +To delete a repository, follow these steps. + +1. Log in to your Grafana server with an account that has the Grafana Admin or Editor flag set. +1. Select **Administration** in the left-side menu and then **Provisioning**. +1. Locate the repository you are interested in. +1. Select the trashcan icon in the right side to delete the chosen entry. +1. Select **Delete** to confirm. + +## Troubleshoot synchronization + +Monitor the **View** status page for synchronization issues and status updates. Common events include: + +- Sync started +- Sync completed +- Sync failed (with error details) +- Sync issues + +### Dashboard sync errors + +- If dashboards are not syncing, check if the repository URL is correct and accessible from the Grafana instance. +- Ensure that the configured repository branch exists and is correctly referenced. +- Check for conflicts in the repository that may prevent syncing. + +### Dashboard import errors + +- Validate the JSON format of the dashboard files before importing. +- If the import fails, check Grafana logs for error messages and troubleshoot accordingly. From 8ef8471b236f69cb3fd2d6c6337f277659eebe73 Mon Sep 17 00:00:00 2001 From: ismail simsek Date: Tue, 22 Apr 2025 18:54:10 +0200 Subject: [PATCH 004/146] Chore: Remove prometheusUsesCombobox feature toggle (#103940) * remove prometheusUsesCombobox feature toggle * betterer * fix the unit test * create MetricsLabelsSection unit tests * fix unit tests * fix unit tests in PromQueryBuilder.test.tsx * prettier * remove timeouts * Revert "remove timeouts" This reverts commit 84af1fd46bad807e9b42a8c1b1ce308700ab36c9. --- .betterer.results | 3 - .../feature-toggles/index.md | 1 - .../src/types/featureToggles.gen.ts | 5 - .../components/VariableQueryEditor.test.tsx | 21 +- packages/grafana-prometheus/src/index.ts | 2 +- .../grafana-prometheus/src/language_utils.ts | 3 +- .../components/MetricCombobox.test.tsx | 18 + .../components/MetricSelect.test.tsx | 198 --------- .../querybuilder/components/MetricSelect.tsx | 401 ------------------ .../components/MetricsLabelsSection.test.tsx | 391 +++++++++++++++++ .../components/MetricsLabelsSection.tsx | 6 +- .../components/PromQueryBuilder.test.tsx | 35 +- .../PromQueryBuilderContainer.test.tsx | 1 - pkg/services/featuremgmt/registry.go | 7 - pkg/services/featuremgmt/toggles_gen.csv | 1 - pkg/services/featuremgmt/toggles_gen.go | 4 - pkg/services/featuremgmt/toggles_gen.json | 3 +- 17 files changed, 459 insertions(+), 641 deletions(-) delete mode 100644 packages/grafana-prometheus/src/querybuilder/components/MetricSelect.test.tsx delete mode 100644 packages/grafana-prometheus/src/querybuilder/components/MetricSelect.tsx create mode 100644 packages/grafana-prometheus/src/querybuilder/components/MetricsLabelsSection.test.tsx diff --git a/.betterer.results b/.betterer.results index f7d69b7f905..8effcfa4d09 100644 --- a/.betterer.results +++ b/.betterer.results @@ -428,9 +428,6 @@ exports[`better eslint`] = { "packages/grafana-prometheus/src/querybuilder/components/LabelParamEditor.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], - "packages/grafana-prometheus/src/querybuilder/components/MetricSelect.tsx:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] - ], "packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilder.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index a0a36e07813..3472114015f 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -75,7 +75,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `alertingQueryAndExpressionsStepMode` | Enables step mode for alerting queries and expressions | Yes | | `useSessionStorageForRedirection` | Use session storage for handling the redirection after login | Yes | | `pluginsSriChecks` | Enables SRI checks for plugin assets | | -| `prometheusUsesCombobox` | Use new **Combobox** component for Prometheus query editor | Yes | | `azureMonitorDisableLogLimit` | Disables the log limit restriction for Azure Monitor when true. The limit is enabled by default. | | | `preinstallAutoUpdate` | Enables automatic updates for pre-installed plugins | Yes | | `reportingUseRawTimeRange` | Uses the original report or dashboard time range instead of making an absolute transformation | Yes | diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index f396e0565d8..54946660dd3 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -762,11 +762,6 @@ export interface FeatureToggles { */ timeRangeProvider?: boolean; /** - * Use new **Combobox** component for Prometheus query editor - * @default true - */ - prometheusUsesCombobox?: boolean; - /** * Disables the log limit restriction for Azure Monitor when true. The limit is enabled by default. * @default false */ diff --git a/packages/grafana-prometheus/src/components/VariableQueryEditor.test.tsx b/packages/grafana-prometheus/src/components/VariableQueryEditor.test.tsx index 5e925ee928e..2e5afaef131 100644 --- a/packages/grafana-prometheus/src/components/VariableQueryEditor.test.tsx +++ b/packages/grafana-prometheus/src/components/VariableQueryEditor.test.tsx @@ -145,10 +145,19 @@ describe('PromVariableQueryEditor', () => { fetchLabelsWithMatch: jest.fn().mockImplementation(() => Promise.resolve({ those: 'those' })), } as Partial as PrometheusLanguageProvider, getDebounceTimeInMilliseconds: jest.fn(), - getTagKeys: jest.fn().mockImplementation(() => Promise.resolve(['this'])), + getTagKeys: jest + .fn() + .mockImplementation(() => Promise.resolve([{ text: 'this', value: 'this', label: 'this' }])), getVariables: jest.fn().mockImplementation(() => []), - metricFindQuery: jest.fn().mockImplementation(() => Promise.resolve(['that'])), - getSeriesLabels: jest.fn().mockImplementation(() => Promise.resolve(['that'])), + metricFindQuery: jest.fn().mockImplementation(() => + Promise.resolve([ + { + text: 'that', + value: 'that', + label: 'that', + }, + ]) + ), } as Partial as PrometheusDatasource, query: { refId: 'test', @@ -291,9 +300,9 @@ describe('PromVariableQueryEditor', () => { await userEvent.type(labelSelect, 'this'); await selectOptionInTest(labelSelect, 'this'); - const metricSelect = screen.getByLabelText('Metric'); - await userEvent.type(metricSelect, 'that'); - await selectOptionInTest(metricSelect, 'that'); + const combobox = screen.getByPlaceholderText('Select metric'); + await userEvent.type(combobox, 'that'); + await userEvent.keyboard('{Enter}'); await waitFor(() => expect(onChange).toHaveBeenCalledWith({ diff --git a/packages/grafana-prometheus/src/index.ts b/packages/grafana-prometheus/src/index.ts index c28a7b78ab5..02da71f652a 100644 --- a/packages/grafana-prometheus/src/index.ts +++ b/packages/grafana-prometheus/src/index.ts @@ -42,7 +42,7 @@ export { QueryPatternsModal } from './querybuilder/QueryPatternsModal'; export { LabelFilterItem } from './querybuilder/components/LabelFilterItem'; export { LabelFilters } from './querybuilder/components/LabelFilters'; export { LabelParamEditor } from './querybuilder/components/LabelParamEditor'; -export { MetricSelect } from './querybuilder/components/MetricSelect'; +export { MetricCombobox } from './querybuilder/components/MetricCombobox'; export { MetricsLabelsSection } from './querybuilder/components/MetricsLabelsSection'; export { NestedQuery } from './querybuilder/components/NestedQuery'; export { NestedQueryList } from './querybuilder/components/NestedQueryList'; diff --git a/packages/grafana-prometheus/src/language_utils.ts b/packages/grafana-prometheus/src/language_utils.ts index 87f03f4dc82..63426517d88 100644 --- a/packages/grafana-prometheus/src/language_utils.ts +++ b/packages/grafana-prometheus/src/language_utils.ts @@ -15,9 +15,10 @@ import { import { addLabelToQuery } from './add_label_to_query'; import { SUGGESTIONS_LIMIT } from './language_provider'; -import { PROMETHEUS_QUERY_BUILDER_MAX_RESULTS } from './querybuilder/components/MetricSelect'; import { PrometheusCacheLevel, PromMetricsMetadata, PromMetricsMetadataItem, RecordingRuleIdentifier } from './types'; +export const PROMETHEUS_QUERY_BUILDER_MAX_RESULTS = 1000; + export const processHistogramMetrics = (metrics: string[]) => { const resultSet: Set = new Set(); const regexp = new RegExp('_bucket($|:)'); diff --git a/packages/grafana-prometheus/src/querybuilder/components/MetricCombobox.test.tsx b/packages/grafana-prometheus/src/querybuilder/components/MetricCombobox.test.tsx index c832a547bdc..906393e1014 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/MetricCombobox.test.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/MetricCombobox.test.tsx @@ -134,6 +134,24 @@ describe('MetricCombobox', () => { expect(screen.queryByRole('button', { name: /open metrics explorer/i })).toBeInTheDocument(); }); + it('displays the default metric value from query prop', () => { + // Render with a query that has a default metric value + render( + + ); + + // The Combobox should display the default metric value + const combobox = screen.getByPlaceholderText('Select metric'); + expect(combobox).toHaveValue('default_metric_value'); + }); + it('opens the metrics explorer when the button is clicked', async () => { render( Promise.resolve([])} />); diff --git a/packages/grafana-prometheus/src/querybuilder/components/MetricSelect.test.tsx b/packages/grafana-prometheus/src/querybuilder/components/MetricSelect.test.tsx deleted file mode 100644 index 1f6780392f7..00000000000 --- a/packages/grafana-prometheus/src/querybuilder/components/MetricSelect.test.tsx +++ /dev/null @@ -1,198 +0,0 @@ -// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/MetricSelect.test.tsx -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; - -import { DataSourceInstanceSettings, MetricFindValue } from '@grafana/data'; -import { selectors } from '@grafana/e2e-selectors'; - -import { PrometheusDatasource } from '../../datasource'; -import { PromOptions } from '../../types'; - -import { - formatPrometheusLabelFilters, - formatPrometheusLabelFiltersToString, - MetricSelect, - MetricSelectProps, -} from './MetricSelect'; - -const instanceSettings = { - url: 'proxied', - id: 1, - user: 'test', - password: 'mupp', - jsonData: { httpMethod: 'GET' }, -} as unknown as DataSourceInstanceSettings; - -const dataSourceMock = new PrometheusDatasource(instanceSettings); -const mockValues = [{ label: 'random_metric' }, { label: 'unique_metric' }, { label: 'more_unique_metric' }]; -// Mock metricFindQuery which will call backend API -//@ts-ignore -dataSourceMock.metricFindQuery = jest.fn((query: string) => { - // Use the label values regex to get the values inside the label_values function call - const labelValuesRegex = /^label_values\((?:(.+),\s*)?([a-zA-Z_][a-zA-Z0-9_]*)\)\s*$/; - const queryValueArray = query.match(labelValuesRegex) as RegExpMatchArray; - const queryValueRaw = queryValueArray[1] as string; - - // Remove the wrapping regex - const queryValue = queryValueRaw.substring(queryValueRaw.indexOf('".*') + 3, queryValueRaw.indexOf('.*"')); - - // Run the regex that we'd pass into prometheus API against the strings in the test - return Promise.resolve( - mockValues - .filter((value) => value.label.match(queryValue)) - .map((result) => { - return { - text: result.label, - }; - }) as MetricFindValue[] - ); -}); - -const props: MetricSelectProps = { - labelsFilters: [], - datasource: dataSourceMock, - query: { - metric: '', - labels: [], - operations: [], - }, - onChange: jest.fn(), - onGetMetrics: jest.fn().mockResolvedValue(mockValues), - metricLookupDisabled: false, -}; - -describe('MetricSelect', () => { - it('shows all metric options', async () => { - render(); - await openMetricSelect(); - await waitFor(() => expect(screen.getByText('random_metric')).toBeInTheDocument()); - await waitFor(() => expect(screen.getByText('unique_metric')).toBeInTheDocument()); - await waitFor(() => expect(screen.getByText('more_unique_metric')).toBeInTheDocument()); - await waitFor(() => expect(screen.getByText('Metrics explorer')).toBeInTheDocument()); - await waitFor(() => expect(screen.getAllByTestId(selectors.components.Select.option)).toHaveLength(4)); - }); - - it('truncates list of metrics to 1000', async () => { - const manyMockValues = [...Array(1001).keys()].map((idx: number) => { - return { label: 'random_metric' + idx }; - }); - - props.onGetMetrics = jest.fn().mockResolvedValue(manyMockValues); - - render(); - await openMetricSelect(); - // the metrics explorer is added as a custom option - const optionsLength = screen.getAllByTestId(selectors.components.Select.option).length; - const optionsLengthMinusMetricsExplorer = optionsLength - 1; - await waitFor(() => expect(optionsLengthMinusMetricsExplorer).toBe(1000)); - }); - - it('shows option to set custom value when typing', async () => { - render(); - await openMetricSelect(); - const input = screen.getByRole('combobox'); - await userEvent.type(input, 'custom value'); - await waitFor(() => expect(screen.getByText('custom value')).toBeInTheDocument()); - }); - - it('shows searched options when typing', async () => { - render(); - await openMetricSelect(); - const input = screen.getByRole('combobox'); - await userEvent.type(input, 'unique'); - const optionsLength = mockValues.length + 1; // the metrics explorer is added as a custom option - await waitFor(() => expect(screen.getAllByTestId(selectors.components.Select.option)).toHaveLength(optionsLength)); - }); - - it('searches on split words', async () => { - render(); - await openMetricSelect(); - const input = screen.getByRole('combobox'); - await userEvent.type(input, 'more unique'); - // the metrics explorer is added as a custom option - await waitFor(() => expect(screen.getAllByTestId(selectors.components.Select.option)).toHaveLength(3)); - }); - - it('searches on multiple split words', async () => { - render(); - await openMetricSelect(); - const input = screen.getByRole('combobox'); - await userEvent.type(input, 'more unique metric'); - // the metrics explorer is added as a custom option - await waitFor(() => expect(screen.getAllByTestId(selectors.components.Select.option)).toHaveLength(3)); - }); - - it('highlights matching string', async () => { - render(); - await openMetricSelect(); - const input = screen.getByRole('combobox'); - await userEvent.type(input, 'more'); - await waitFor(() => expect(document.querySelectorAll('mark')).toHaveLength(1)); - }); - - it('highlights multiple matching strings in 1 input row', async () => { - render(); - await openMetricSelect(); - const input = screen.getByRole('combobox'); - await userEvent.type(input, 'more metric'); - await waitFor(() => expect(document.querySelectorAll('mark')).toHaveLength(2)); - }); - - it('highlights multiple matching strings in multiple input rows', async () => { - render(); - await openMetricSelect(); - const input = screen.getByRole('combobox'); - await userEvent.type(input, 'unique metric'); - await waitFor(() => expect(document.querySelectorAll('mark')).toHaveLength(4)); - }); - - it('does not highlight matching string in create option', async () => { - render(); - await openMetricSelect(); - const input = screen.getByRole('combobox'); - await userEvent.type(input, 'new'); - await waitFor(() => expect(document.querySelector('mark')).not.toBeInTheDocument()); - }); - - it('label filters properly join', () => { - const query = formatPrometheusLabelFilters([ - { - value: 'value', - label: 'label', - op: '=', - }, - { - value: 'value2', - label: 'label2', - op: '=', - }, - ]); - query.forEach((label) => { - expect(label.includes(',', 0)); - }); - }); - it('label filter creation', () => { - const labels = [ - { - value: 'value', - label: 'label', - op: '=', - }, - { - value: 'value2', - label: 'label2', - op: '=', - }, - ]; - - const queryString = formatPrometheusLabelFiltersToString('query', labels); - queryString.split(',').forEach((queryChunk) => { - expect(queryChunk.length).toBeGreaterThan(1); // must be longer then ',' - }); - }); -}); - -async function openMetricSelect() { - const select = screen.getByText('Select metric').parentElement!; - await userEvent.click(select); -} diff --git a/packages/grafana-prometheus/src/querybuilder/components/MetricSelect.tsx b/packages/grafana-prometheus/src/querybuilder/components/MetricSelect.tsx deleted file mode 100644 index 0de1f240694..00000000000 --- a/packages/grafana-prometheus/src/querybuilder/components/MetricSelect.tsx +++ /dev/null @@ -1,401 +0,0 @@ -// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/components/MetricSelect.tsx -import { css } from '@emotion/css'; -import debounce from 'debounce-promise'; -import { RefCallback, useCallback, useState } from 'react'; -import * as React from 'react'; -import Highlighter from 'react-highlight-words'; - -import { GrafanaTheme2, SelectableValue, toOption } from '@grafana/data'; -import { selectors } from '@grafana/e2e-selectors'; -import { EditorField, EditorFieldGroup } from '@grafana/plugin-ui'; -import { - AsyncSelect, - Button, - FormatOptionLabelMeta, - getSelectStyles, - Icon, - InlineField, - InlineFieldRow, - ScrollContainer, - SelectMenuOptions, - useStyles2, - useTheme2, -} from '@grafana/ui'; - -import { PrometheusDatasource } from '../../datasource'; -import { truncateResult } from '../../language_utils'; -import { regexifyLabelValuesQueryString } from '../parsingUtils'; -import { QueryBuilderLabelFilter } from '../shared/types'; -import { PromVisualQuery } from '../types'; - -import { MetricsModal } from './metrics-modal/MetricsModal'; -import { tracking } from './metrics-modal/state/helpers'; - -// We are matching words split with space -const splitSeparator = ' '; - -export interface MetricSelectProps { - metricLookupDisabled: boolean; - query: PromVisualQuery; - onChange: (query: PromVisualQuery) => void; - onGetMetrics: () => Promise; - datasource: PrometheusDatasource; - labelsFilters: QueryBuilderLabelFilter[]; - onBlur?: () => void; - variableEditor?: boolean; -} - -export const PROMETHEUS_QUERY_BUILDER_MAX_RESULTS = 1000; - -export function MetricSelect({ - datasource, - query, - onChange, - onGetMetrics, - labelsFilters, - metricLookupDisabled, - onBlur, - variableEditor, -}: Readonly) { - const styles = useStyles2(getStyles); - const [state, setState] = useState<{ - metrics?: SelectableValue[]; - isLoading?: boolean; - metricsModalOpen?: boolean; - initialMetrics?: string[]; - resultsTruncated?: boolean; - }>({}); - - const metricsModalOption: SelectableValue[] = [ - { - value: 'BrowseMetrics', - label: 'Metrics explorer', - description: 'Browse and filter all metrics and metadata with a fuzzy search', - }, - ]; - - const customFilterOption = useCallback((option: SelectableValue, searchQuery: string) => { - const label = option.label ?? option.value; - if (!label) { - return false; - } - - // custom value is not a string label but a react node - if (!label.toLowerCase) { - return true; - } - - const searchWords = searchQuery.split(splitSeparator); - - return searchWords.reduce((acc, cur) => { - const matcheSearch = label.toLowerCase().includes(cur.toLowerCase()); - const browseOption = label === 'Metrics explorer'; - return acc && (matcheSearch || browseOption); - }, true); - }, []); - - const formatOptionLabel = useCallback( - (option: SelectableValue, meta: FormatOptionLabelMeta) => { - // For newly created custom value we don't want to add highlight - if (option['__isNew__']) { - return option.label; - } - // only matches on input, does not match on regex - // look into matching for regex input - return ( - - ); - }, - [styles.highlight] - ); - - /** - * Reformat the query string and label filters to return all valid results for current query editor state - */ - const formatKeyValueStringsForLabelValuesQuery = ( - query: string, - labelsFilters?: QueryBuilderLabelFilter[] - ): string => { - const queryString = regexifyLabelValuesQueryString(query); - - return formatPrometheusLabelFiltersToString(queryString, labelsFilters); - }; - - /** - * Gets label_values response from prometheus API for current autocomplete query string and any existing labels filters - */ - const getMetricLabels = (query: string) => { - // Since some customers can have millions of metrics, whenever the user changes the autocomplete text we want to call the backend and request all metrics that match the current query string - const results = datasource.metricFindQuery(formatKeyValueStringsForLabelValuesQuery(query, labelsFilters)); - return results.then((results) => { - const resultsLength = results.length; - truncateResult(results); - - if (resultsLength > results.length) { - setState({ ...state, resultsTruncated: true }); - } else { - setState({ ...state, resultsTruncated: false }); - } - - const resultsOptions = results.map((result) => { - return { - label: result.text, - value: result.text, - }; - }); - - return [...metricsModalOption, ...resultsOptions]; - }); - }; - - // When metric and label lookup is disabled we won't request labels - const metricLookupDisabledSearch = () => Promise.resolve([]); - - const debouncedSearch = debounce( - (query: string) => getMetricLabels(query), - datasource.getDebounceTimeInMilliseconds() - ); - - // No type found for the common select props so typing as any - // https://github.com/grafana/grafana/blob/main/packages/grafana-ui/src/components/Select/SelectBase.tsx/#L212-L263 - // eslint-disable-next-line - const CustomOption = (props: any) => { - const option = props.data; - - if (option.value === 'BrowseMetrics') { - const isFocused = props.isFocused ? styles.focus : ''; - - return ( - // TODO: fix keyboard a11y - // eslint-disable-next-line jsx-a11y/no-static-element-interactions -
{ - // if there is no metric and the m.e. is enabled, open the modal - if (e.code === 'Enter') { - setState({ ...state, metricsModalOpen: true }); - } - }} - > - { -
-
-
{option.label}
-
{option.description}
-
- -
- } -
- ); - } - - return SelectMenuOptions(props); - }; - - interface SelectMenuProps { - maxHeight: number; - innerRef: RefCallback; - innerProps: {}; - } - - const CustomMenu = ({ children, maxHeight, innerRef, innerProps }: React.PropsWithChildren) => { - const theme = useTheme2(); - const stylesMenu = getSelectStyles(theme); - - // Show the results trucated warning only if the options are loaded and the results are truncated - // The children are a react node(options loading node) or an array(not a valid element) - const optionsLoaded = !React.isValidElement(children) && state.resultsTruncated; - - return ( -
- - {children} - - {optionsLoaded && ( -
-
- Only the top 1000 metrics are displayed in the metric select. Use the metrics explorer to view all - metrics. -
-
- )} -
- ); - }; - - const asyncSelect = () => { - return ( - { - if (metricLookupDisabled) { - return; - } - setState({ isLoading: true }); - const metrics = await onGetMetrics(); - const initialMetrics: string[] = metrics.map((m) => m.value); - const resultsLength = metrics.length; - - if (metrics.length > PROMETHEUS_QUERY_BUILDER_MAX_RESULTS) { - truncateResult(metrics); - } - - setState({ - // add the modal button option to the options - metrics: [...metricsModalOption, ...metrics], - isLoading: undefined, - // pass the initial metrics into the metrics explorer - initialMetrics: initialMetrics, - resultsTruncated: resultsLength > metrics.length, - }); - }} - loadOptions={metricLookupDisabled ? metricLookupDisabledSearch : debouncedSearch} - isLoading={state.isLoading} - defaultOptions={state.metrics ?? Array.from(new Array(25), () => ({ value: '' }))} // We need empty values when `state.metrics` is falsy in order for the select to correctly determine top/bottom placement - onChange={(input) => { - const value = input?.value; - if (value) { - // if there is no metric and the value is the custom m.e. option, open the modal - if (value === 'BrowseMetrics') { - tracking('grafana_prometheus_metric_encyclopedia_open', null, '', query); - setState({ ...state, metricsModalOpen: true }); - } else { - onChange({ ...query, metric: value }); - } - } else { - onChange({ ...query, metric: '' }); - } - }} - components={{ Option: CustomOption, MenuList: CustomMenu }} - onBlur={onBlur} - /> - ); - }; - - return ( - <> - {!datasource.lookupsDisabled && state.metricsModalOpen && ( - setState({ ...state, metricsModalOpen: false })} - query={query} - onChange={onChange} - initialMetrics={state.initialMetrics ?? []} - /> - )} - {/* format the ui for either the query editor or the variable editor */} - {variableEditor ? ( - - Optional: returns a list of label values for the label name in the specified metric.
} - > - {asyncSelect()} - - - ) : ( - - {asyncSelect()} - - )} - - ); -} - -const getStyles = (theme: GrafanaTheme2) => ({ - select: css({ - minWidth: '125px', - }), - highlight: css({ - label: 'select__match-highlight', - background: 'inherit', - padding: 'inherit', - color: theme.colors.warning.contrastText, - backgroundColor: theme.colors.warning.main, - }), - customOption: css({ - padding: '8px', - display: 'flex', - justifyContent: 'space-between', - cursor: 'pointer', - ':hover': { - backgroundColor: theme.colors.emphasize(theme.colors.background.primary, 0.1), - }, - }), - customOptionlabel: css({ - color: theme.colors.text.primary, - }), - customOptionDesc: css({ - color: theme.colors.text.secondary, - fontSize: theme.typography.size.xs, - opacity: '50%', - }), - focus: css({ - backgroundColor: theme.colors.emphasize(theme.colors.background.primary, 0.1), - }), - customOptionWidth: css({ - minWidth: '400px', - }), - customMenuFooter: css({ - flex: 0, - display: 'flex', - justifyContent: 'space-between', - padding: theme.spacing(1.5), - borderTop: `1px solid ${theme.colors.border.weak}`, - color: theme.colors.text.secondary, - }), - customMenuContainer: css({ - display: 'flex', - flexDirection: 'column', - background: theme.colors.background.primary, - boxShadow: theme.shadows.z3, - }), -}); - -export const formatPrometheusLabelFiltersToString = ( - queryString: string, - labelsFilters: QueryBuilderLabelFilter[] | undefined -): string => { - const filterArray = labelsFilters ? formatPrometheusLabelFilters(labelsFilters) : []; - - return `label_values({__name__=~".*${queryString}"${filterArray ? filterArray.join('') : ''}},__name__)`; -}; - -export const formatPrometheusLabelFilters = (labelsFilters: QueryBuilderLabelFilter[]): string[] => { - return labelsFilters.map((label) => { - return `,${label.label}="${label.value}"`; - }); -}; diff --git a/packages/grafana-prometheus/src/querybuilder/components/MetricsLabelsSection.test.tsx b/packages/grafana-prometheus/src/querybuilder/components/MetricsLabelsSection.test.tsx new file mode 100644 index 00000000000..324e0c87af8 --- /dev/null +++ b/packages/grafana-prometheus/src/querybuilder/components/MetricsLabelsSection.test.tsx @@ -0,0 +1,391 @@ +import { render, screen } from '@testing-library/react'; + +import { dateTime } from '@grafana/data'; + +import { PrometheusDatasource } from '../../datasource'; +import { PromVisualQuery } from '../types'; + +import { MetricsLabelsSection } from './MetricsLabelsSection'; + +// Mock dependencies +jest.mock('./MetricCombobox', () => ({ + MetricCombobox: jest.fn(() =>
Metric Combobox
), +})); + +jest.mock('./LabelFilters', () => ({ + LabelFilters: jest.fn(() =>
Label Filters
), +})); + +// Create mock for PrometheusDatasource +const createMockDatasource = () => { + const datasource = { + uid: 'prometheus', + getVariables: jest.fn().mockReturnValue(['$var1', '$var2']), + interpolateString: jest.fn((str) => str), + hasLabelsMatchAPISupport: jest.fn().mockReturnValue(true), + getDebounceTimeInMilliseconds: jest.fn().mockReturnValue(300), + lookupsDisabled: false, + languageProvider: { + fetchLabels: jest.fn().mockResolvedValue({}), + getLabelKeys: jest.fn().mockReturnValue(['label1', 'label2']), + fetchLabelsWithMatch: jest.fn().mockResolvedValue({ label1: [], label2: [] }), + fetchSeries: jest.fn().mockResolvedValue([{ label1: 'value1' }]), + fetchSeriesValuesWithMatch: jest.fn().mockResolvedValue(['value1', 'value2']), + getLabelValues: jest.fn().mockResolvedValue(['value1', 'value2']), + getSeries: jest.fn().mockResolvedValue({ __name__: ['metric1', 'metric2'] }), + loadMetricsMetadata: jest.fn().mockResolvedValue({}), + metricsMetadata: { metric1: { type: 'counter', help: 'help text' } }, + }, + }; + return datasource as unknown as PrometheusDatasource; +}; + +const defaultQuery: PromVisualQuery = { + metric: 'metric1', + labels: [{ label: 'label1', op: '=', value: 'value1' }], + operations: [], +}; + +const defaultTimeRange = { + from: dateTime(1000), + to: dateTime(2000), + raw: { + from: 'now-1h', + to: 'now', + }, +}; + +describe('MetricsLabelsSection', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render MetricCombobox and LabelFilters', () => { + const onChange = jest.fn(); + const datasource = createMockDatasource(); + + render( + + ); + + expect(screen.getByTestId('metric-combobox')).toBeInTheDocument(); + expect(screen.getByTestId('label-filters')).toBeInTheDocument(); + }); + + it('should pass correct props to MetricCombobox', async () => { + const onChange = jest.fn(); + const onBlur = jest.fn(); + const datasource = createMockDatasource(); + const { MetricCombobox } = require('./MetricCombobox'); + + render( + + ); + + // Check that MetricCombobox was called with correct props + expect(MetricCombobox).toHaveBeenCalledWith( + expect.objectContaining({ + query: defaultQuery, + onChange: onChange, + datasource: datasource, + labelsFilters: defaultQuery.labels, + metricLookupDisabled: false, + onBlur: onBlur, + variableEditor: undefined, + }), + expect.anything() + ); + }); + + it('should pass correct props to LabelFilters', async () => { + const onChange = jest.fn(); + const datasource = createMockDatasource(); + const { LabelFilters } = require('./LabelFilters'); + + render( + + ); + + // Check that LabelFilters was called with correct props + expect(LabelFilters).toHaveBeenCalledWith( + expect.objectContaining({ + debounceDuration: 300, + labelsFilters: defaultQuery.labels, + variableEditor: undefined, + }), + expect.anything() + ); + }); + + it('should handle onChangeLabels correctly', async () => { + const onChange = jest.fn(); + const datasource = createMockDatasource(); + const { LabelFilters } = require('./LabelFilters'); + + render( + + ); + + // Extract the onChangeLabels callback + const onChangeLabelsCallback = LabelFilters.mock.calls[0][0].onChange; + + // Call it with new labels + const newLabels = [{ label: 'newLabel', op: '=', value: 'newValue' }]; + onChangeLabelsCallback(newLabels); + + // Check that onChange was called with updated query + expect(onChange).toHaveBeenCalledWith({ + ...defaultQuery, + labels: newLabels, + }); + }); + + it('should handle withTemplateVariableOptions correctly', async () => { + const onChange = jest.fn(); + const datasource = createMockDatasource(); + const { LabelFilters } = require('./LabelFilters'); + + render( + + ); + + // Extract the onGetLabelNames callback + const onGetLabelNamesCallback = LabelFilters.mock.calls[0][0].onGetLabelNames; + + // Prepare a test label filter + const forLabel = { label: 'test', op: '=', value: '' }; + + // Call it + const result = await onGetLabelNamesCallback(forLabel); + + // Check that variables were included in the result + expect(result).toContainEqual(expect.objectContaining({ label: '$var1', value: '$var1' })); + expect(result).toContainEqual(expect.objectContaining({ label: '$var2', value: '$var2' })); + }); + + it('should handle onGetLabelNames with no metric correctly', async () => { + const onChange = jest.fn(); + const datasource = createMockDatasource(); + const { LabelFilters } = require('./LabelFilters'); + const queryWithoutMetric = { ...defaultQuery, metric: '' }; + + render( + + ); + + // Extract the onGetLabelNames callback + const onGetLabelNamesCallback = LabelFilters.mock.calls[0][0].onGetLabelNames; + + // Call it + await onGetLabelNamesCallback({}); + + // Check that fetchLabels was called + expect(datasource.languageProvider.fetchLabels).toHaveBeenCalledWith(defaultTimeRange); + // Check that getLabelKeys was called + expect(datasource.languageProvider.getLabelKeys).toHaveBeenCalled(); + }); + + it('should handle onGetLabelNames with metric correctly', async () => { + const onChange = jest.fn(); + const datasource = createMockDatasource(); + const { LabelFilters } = require('./LabelFilters'); + + render( + + ); + + // Extract the onGetLabelNames callback + const onGetLabelNamesCallback = LabelFilters.mock.calls[0][0].onGetLabelNames; + + // Call it + await onGetLabelNamesCallback({}); + + // Check that fetchLabelsWithMatch was called + expect(datasource.languageProvider.fetchLabelsWithMatch).toHaveBeenCalled(); + }); + + it('should handle getLabelValuesAutocompleteSuggestions correctly', async () => { + const onChange = jest.fn(); + const datasource = createMockDatasource(); + const { LabelFilters } = require('./LabelFilters'); + + render( + + ); + + // Extract the getLabelValuesAutofillSuggestions callback + const getLabelValuesCallback = LabelFilters.mock.calls[0][0].getLabelValuesAutofillSuggestions; + + // Call it + await getLabelValuesCallback('val', 'label1'); + + // Check that fetchSeriesValuesWithMatch was called (since hasLabelsMatchAPISupport is true) + expect(datasource.languageProvider.fetchSeriesValuesWithMatch).toHaveBeenCalled(); + }); + + it('should handle onGetLabelValues with no metric correctly', async () => { + const onChange = jest.fn(); + const datasource = createMockDatasource(); + const { LabelFilters } = require('./LabelFilters'); + const queryWithoutMetric = { ...defaultQuery, metric: '' }; + + render( + + ); + + // Extract the onGetLabelValues callback + const onGetLabelValuesCallback = LabelFilters.mock.calls[0][0].onGetLabelValues; + + // Call it + await onGetLabelValuesCallback({ label: 'label1' }); + + // Check that getLabelValues was called + expect(datasource.languageProvider.getLabelValues).toHaveBeenCalledWith(defaultTimeRange, 'label1'); + }); + + it('should handle onGetLabelValues with metric correctly', async () => { + const onChange = jest.fn(); + const datasource = createMockDatasource(); + const { LabelFilters } = require('./LabelFilters'); + + render( + + ); + + // Extract the onGetLabelValues callback + const onGetLabelValuesCallback = LabelFilters.mock.calls[0][0].onGetLabelValues; + + // Call it + await onGetLabelValuesCallback({ label: 'label1' }); + + // Check that fetchSeriesValuesWithMatch was called (since hasLabelsMatchAPISupport is true) + expect(datasource.languageProvider.fetchSeriesValuesWithMatch).toHaveBeenCalled(); + }); + + it('should handle onGetLabelValues with no label correctly', async () => { + const onChange = jest.fn(); + const datasource = createMockDatasource(); + const { LabelFilters } = require('./LabelFilters'); + + render( + + ); + + // Extract the onGetLabelValues callback + const onGetLabelValuesCallback = LabelFilters.mock.calls[0][0].onGetLabelValues; + + // Call it with no label + const result = await onGetLabelValuesCallback({}); + + // In reality, the component has already added the template variables to the result + // Let's check that the result includes the template variables + expect(result).toContainEqual(expect.objectContaining({ label: '$var1', value: '$var1' })); + expect(result).toContainEqual(expect.objectContaining({ label: '$var2', value: '$var2' })); + }); + + it('should handle onGetMetrics correctly', async () => { + const onChange = jest.fn(); + const datasource = createMockDatasource(); + const { MetricCombobox } = require('./MetricCombobox'); + + render( + + ); + + // Extract the onGetMetrics callback + const onGetMetricsCallback = MetricCombobox.mock.calls[0][0].onGetMetrics; + + // Call it + const result = await onGetMetricsCallback(); + + // Check that we get back variables and metrics + expect(result).toContainEqual(expect.objectContaining({ label: '$var1', value: '$var1' })); + expect(result).toContainEqual(expect.objectContaining({ label: '$var2', value: '$var2' })); + // Metrics should be included too, but they come from the mocked getSeries or getLabelValues + }); + + it('should load metrics metadata if not present', async () => { + const onChange = jest.fn(); + const datasource = createMockDatasource(); + datasource.languageProvider.metricsMetadata = undefined; + + render( + + ); + + const { MetricCombobox } = require('./MetricCombobox'); + const onGetMetricsCallback = MetricCombobox.mock.calls[0][0].onGetMetrics; + + // Call it + await onGetMetricsCallback(); + + // loadMetricsMetadata should be called + expect(datasource.languageProvider.loadMetricsMetadata).toHaveBeenCalled(); + }); +}); diff --git a/packages/grafana-prometheus/src/querybuilder/components/MetricsLabelsSection.tsx b/packages/grafana-prometheus/src/querybuilder/components/MetricsLabelsSection.tsx index f6f62ffb54b..feda40fe881 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/MetricsLabelsSection.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/MetricsLabelsSection.tsx @@ -2,7 +2,6 @@ import { useCallback } from 'react'; import { SelectableValue, TimeRange } from '@grafana/data'; -import { config } from '@grafana/runtime'; import { PrometheusDatasource } from '../../datasource'; import { getMetadataString } from '../../language_provider'; @@ -14,7 +13,6 @@ import { PromVisualQuery } from '../types'; import { LabelFilters } from './LabelFilters'; import { MetricCombobox } from './MetricCombobox'; -import { MetricSelect } from './MetricSelect'; export interface MetricsLabelsSectionProps { query: PromVisualQuery; @@ -196,11 +194,9 @@ export function MetricsLabelsSection({ return withTemplateVariableOptions(getMetrics(datasource, query, timeRange)); }, [datasource, query, timeRange, withTemplateVariableOptions]); - const MetricSelectComponent = config.featureToggles.prometheusUsesCombobox ? MetricCombobox : MetricSelect; - return ( <> - { it('renders all the query sections', async () => { setup(bugQuery); - expect(screen.getByText('random_metric')).toBeInTheDocument(); + expect(screen.getByDisplayValue('random_metric')).toBeInTheDocument(); expect(screen.getByText('localhost:9090')).toBeInTheDocument(); expect(screen.getByText('Rate')).toBeInTheDocument(); const sumBys = screen.getAllByTestId('operations.1.wrapper'); @@ -167,8 +167,16 @@ describe('PromQueryBuilder', () => { operations: [], }); await openMetricSelect(container); - await userEvent.click(screen.getByText('histogram_metric_bucket')); - await waitFor(() => expect(screen.getByText('hint: add histogram_quantile')).toBeInTheDocument()); + + // We need to trigger the option selection to show the hint + // Just press Enter to select the current option (which should be our metric) + const input = screen.getByTestId('data-testid metric select'); + await userEvent.type(input, '{enter}'); + + // Now check for the hint + await waitFor(() => { + expect(screen.getByText('hint: add histogram_quantile')).toBeInTheDocument(); + }); }); it('shows hints for counter metrics', async () => { @@ -178,7 +186,13 @@ describe('PromQueryBuilder', () => { operations: [], }); await openMetricSelect(container); - await userEvent.click(screen.getByText('histogram_metric_sum')); + + // We need to trigger the option selection to show the hint + // Just press Enter to select the current option (which should be our metric) + const input = screen.getByTestId('data-testid metric select'); + await userEvent.type(input, '{enter}'); + + // Now check for the hint await waitFor(() => expect(screen.getByText('hint: add rate')).toBeInTheDocument()); }); @@ -200,7 +214,13 @@ describe('PromQueryBuilder', () => { data ); await openMetricSelect(container); - await userEvent.click(screen.getByText('histogram_metric_sum')); + + // We need to trigger the option selection to show the hint + // Just press Enter to select the current option (which should be our metric) + const input = screen.getByTestId('data-testid metric select'); + await userEvent.type(input, '{enter}'); + + // Now check for the hints - should be multiple in this case await waitFor(() => expect(screen.getAllByText(/hint:/)).toHaveLength(2)); }); @@ -354,9 +374,12 @@ function setup( } async function openMetricSelect(container: HTMLElement) { - const select = container.querySelector('#prometheus-metric-select'); + const select = container.querySelector('[data-testid="data-testid metric select"]'); if (select) { await userEvent.click(select); + // Also focus to ensure callbacks are triggered + await userEvent.type(select, ' '); + await userEvent.clear(select); } } diff --git a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderContainer.test.tsx b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderContainer.test.tsx index 835879214b9..6225c69db3d 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderContainer.test.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderContainer.test.tsx @@ -17,7 +17,6 @@ describe('PromQueryBuilderContainer', () => { it('translates query between string and model', async () => { const { props } = setup({ expr: 'rate(metric_test{job="testjob"}[$__rate_interval])' }); - expect(screen.getByText('metric_test')).toBeInTheDocument(); await addOperationInQueryBuilder('Range functions', 'Rate'); // extra fields here are for storing metrics explorer settings. Future work: store these in local storage. expect(props.onChange).toHaveBeenCalledWith({ diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index d75409f7155..941781e000f 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1304,13 +1304,6 @@ var ( Stage: FeatureStageExperimental, Owner: grafanaFrontendPlatformSquad, }, - { - Name: "prometheusUsesCombobox", - Description: "Use new **Combobox** component for Prometheus query editor", - Stage: FeatureStageGeneralAvailability, - Owner: grafanaOSSBigTent, - Expression: "true", // enabled by default - }, { Name: "azureMonitorDisableLogLimit", Description: "Disables the log limit restriction for Azure Monitor when true. The limit is enabled by default.", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 8816160b388..bafd477aea4 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -170,7 +170,6 @@ managedDualWriter,experimental,@grafana/search-and-storage,false,false,false pluginsSriChecks,GA,@grafana/plugins-platform-backend,false,false,false unifiedStorageBigObjectsSupport,experimental,@grafana/search-and-storage,false,false,false timeRangeProvider,experimental,@grafana/grafana-frontend-platform,false,false,false -prometheusUsesCombobox,GA,@grafana/oss-big-tent,false,false,false azureMonitorDisableLogLimit,GA,@grafana/partner-datasources,false,false,false preinstallAutoUpdate,GA,@grafana/plugins-platform-backend,false,false,false playlistsReconciler,experimental,@grafana/grafana-app-platform-squad,false,true,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index ec9705ab011..d190c505986 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -691,10 +691,6 @@ const ( // Enables time pickers sync FlagTimeRangeProvider = "timeRangeProvider" - // FlagPrometheusUsesCombobox - // Use new **Combobox** component for Prometheus query editor - FlagPrometheusUsesCombobox = "prometheusUsesCombobox" - // FlagAzureMonitorDisableLogLimit // Disables the log limit restriction for Azure Monitor when true. The limit is enabled by default. FlagAzureMonitorDisableLogLimit = "azureMonitorDisableLogLimit" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 57a7ab30714..29d5afd6581 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2657,7 +2657,8 @@ "metadata": { "name": "prometheusUsesCombobox", "resourceVersion": "1743693517832", - "creationTimestamp": "2024-10-23T11:18:33Z" + "creationTimestamp": "2024-10-23T11:18:33Z", + "deletionTimestamp": "2025-04-11T19:25:28Z" }, "spec": { "description": "Use new **Combobox** component for Prometheus query editor", From a438b192d3c0c4b58e87a340c393f199d568c790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Jim=C3=A9nez=20S=C3=A1nchez?= Date: Tue, 22 Apr 2025 19:20:29 +0200 Subject: [PATCH 005/146] Provisioning: unit test repository/github package (#104310) * Add unit tests IsAuthenticated * Add unit tests RepoExists * Add unit tests GetContents * Add initial unit tests GetTree * Add unit tests for CreateFile * Add unit test UpdateFile * Add unit tests DeleteFile * Add unit tests for Commits * Add unit tests for helpers * Add unit test CompareCommits * Add GetBranch tests * Add unit tests BranchExists and CreateBranch * Add unit tests Webhooks * Remove unused code * Add unit tests CommentPullRequest * Add more cases for GetTree * Complete coverage * Fix linting --- .../jobs/pullrequest/mock_pullrequest_repo.go | 97 - .../provisioning/jobs/pullrequest/worker.go | 2 - .../apis/provisioning/repository/github.go | 21 - .../provisioning/repository/github/client.go | 2 - .../provisioning/repository/github/impl.go | 75 +- .../repository/github/impl_test.go | 3859 +++++++++++++++++ .../repository/github/mock_client.go | 99 - .../provisioning/repository/local_test.go | 1 + 8 files changed, 3877 insertions(+), 279 deletions(-) create mode 100644 pkg/registry/apis/provisioning/repository/github/impl_test.go diff --git a/pkg/registry/apis/provisioning/jobs/pullrequest/mock_pullrequest_repo.go b/pkg/registry/apis/provisioning/jobs/pullrequest/mock_pullrequest_repo.go index 882ee50cbe9..08a6f3dff61 100644 --- a/pkg/registry/apis/provisioning/jobs/pullrequest/mock_pullrequest_repo.go +++ b/pkg/registry/apis/provisioning/jobs/pullrequest/mock_pullrequest_repo.go @@ -24,53 +24,6 @@ func (_m *MockPullRequestRepo) EXPECT() *MockPullRequestRepo_Expecter { return &MockPullRequestRepo_Expecter{mock: &_m.Mock} } -// ClearAllPullRequestFileComments provides a mock function with given fields: ctx, pr -func (_m *MockPullRequestRepo) ClearAllPullRequestFileComments(ctx context.Context, pr int) error { - ret := _m.Called(ctx, pr) - - if len(ret) == 0 { - panic("no return value specified for ClearAllPullRequestFileComments") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, int) error); ok { - r0 = rf(ctx, pr) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockPullRequestRepo_ClearAllPullRequestFileComments_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClearAllPullRequestFileComments' -type MockPullRequestRepo_ClearAllPullRequestFileComments_Call struct { - *mock.Call -} - -// ClearAllPullRequestFileComments is a helper method to define mock.On call -// - ctx context.Context -// - pr int -func (_e *MockPullRequestRepo_Expecter) ClearAllPullRequestFileComments(ctx interface{}, pr interface{}) *MockPullRequestRepo_ClearAllPullRequestFileComments_Call { - return &MockPullRequestRepo_ClearAllPullRequestFileComments_Call{Call: _e.mock.On("ClearAllPullRequestFileComments", ctx, pr)} -} - -func (_c *MockPullRequestRepo_ClearAllPullRequestFileComments_Call) Run(run func(ctx context.Context, pr int)) *MockPullRequestRepo_ClearAllPullRequestFileComments_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(int)) - }) - return _c -} - -func (_c *MockPullRequestRepo_ClearAllPullRequestFileComments_Call) Return(_a0 error) *MockPullRequestRepo_ClearAllPullRequestFileComments_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockPullRequestRepo_ClearAllPullRequestFileComments_Call) RunAndReturn(run func(context.Context, int) error) *MockPullRequestRepo_ClearAllPullRequestFileComments_Call { - _c.Call.Return(run) - return _c -} - // CommentPullRequest provides a mock function with given fields: ctx, pr, comment func (_m *MockPullRequestRepo) CommentPullRequest(ctx context.Context, pr int, comment string) error { ret := _m.Called(ctx, pr, comment) @@ -119,56 +72,6 @@ func (_c *MockPullRequestRepo_CommentPullRequest_Call) RunAndReturn(run func(con return _c } -// CommentPullRequestFile provides a mock function with given fields: ctx, pr, path, ref, comment -func (_m *MockPullRequestRepo) CommentPullRequestFile(ctx context.Context, pr int, path string, ref string, comment string) error { - ret := _m.Called(ctx, pr, path, ref, comment) - - if len(ret) == 0 { - panic("no return value specified for CommentPullRequestFile") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, int, string, string, string) error); ok { - r0 = rf(ctx, pr, path, ref, comment) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockPullRequestRepo_CommentPullRequestFile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CommentPullRequestFile' -type MockPullRequestRepo_CommentPullRequestFile_Call struct { - *mock.Call -} - -// CommentPullRequestFile is a helper method to define mock.On call -// - ctx context.Context -// - pr int -// - path string -// - ref string -// - comment string -func (_e *MockPullRequestRepo_Expecter) CommentPullRequestFile(ctx interface{}, pr interface{}, path interface{}, ref interface{}, comment interface{}) *MockPullRequestRepo_CommentPullRequestFile_Call { - return &MockPullRequestRepo_CommentPullRequestFile_Call{Call: _e.mock.On("CommentPullRequestFile", ctx, pr, path, ref, comment)} -} - -func (_c *MockPullRequestRepo_CommentPullRequestFile_Call) Run(run func(ctx context.Context, pr int, path string, ref string, comment string)) *MockPullRequestRepo_CommentPullRequestFile_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(int), args[2].(string), args[3].(string), args[4].(string)) - }) - return _c -} - -func (_c *MockPullRequestRepo_CommentPullRequestFile_Call) Return(_a0 error) *MockPullRequestRepo_CommentPullRequestFile_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockPullRequestRepo_CommentPullRequestFile_Call) RunAndReturn(run func(context.Context, int, string, string, string) error) *MockPullRequestRepo_CommentPullRequestFile_Call { - _c.Call.Return(run) - return _c -} - // CompareFiles provides a mock function with given fields: ctx, base, ref func (_m *MockPullRequestRepo) CompareFiles(ctx context.Context, base string, ref string) ([]repository.VersionedFileChange, error) { ret := _m.Called(ctx, base, ref) diff --git a/pkg/registry/apis/provisioning/jobs/pullrequest/worker.go b/pkg/registry/apis/provisioning/jobs/pullrequest/worker.go index 28db3d37f06..b52c4854b2a 100644 --- a/pkg/registry/apis/provisioning/jobs/pullrequest/worker.go +++ b/pkg/registry/apis/provisioning/jobs/pullrequest/worker.go @@ -19,8 +19,6 @@ type PullRequestRepo interface { Config() *provisioning.Repository Read(ctx context.Context, path, ref string) (*repository.FileInfo, error) CompareFiles(ctx context.Context, base, ref string) ([]repository.VersionedFileChange, error) - ClearAllPullRequestFileComments(ctx context.Context, pr int) error - CommentPullRequestFile(ctx context.Context, pr int, path string, ref string, comment string) error CommentPullRequest(ctx context.Context, pr int, comment string) error } diff --git a/pkg/registry/apis/provisioning/repository/github.go b/pkg/registry/apis/provisioning/repository/github.go index f303bbeac94..577136fc94a 100644 --- a/pkg/registry/apis/provisioning/repository/github.go +++ b/pkg/registry/apis/provisioning/repository/github.go @@ -740,33 +740,12 @@ func (r *githubRepository) CompareFiles(ctx context.Context, base, ref string) ( return changes, nil } -// ClearAllPullRequestFileComments clears all comments on a pull request -func (r *githubRepository) ClearAllPullRequestFileComments(ctx context.Context, prNumber int) error { - ctx, _ = r.logger(ctx, "") - return r.gh.ClearAllPullRequestFileComments(ctx, r.owner, r.repo, prNumber) -} - // CommentPullRequest adds a comment to a pull request. func (r *githubRepository) CommentPullRequest(ctx context.Context, prNumber int, comment string) error { ctx, _ = r.logger(ctx, "") return r.gh.CreatePullRequestComment(ctx, r.owner, r.repo, prNumber, comment) } -// CommentPullRequestFile lints a file and comments the issues found. -func (r *githubRepository) CommentPullRequestFile(ctx context.Context, prNumber int, path, ref, comment string) error { - ctx, _ = r.logger(ctx, ref) - fileComment := pgh.FileComment{ - Content: comment, - Path: path, - Position: 1, // create a top-level comment - Ref: ref, - } - - // FIXME: comment with Grafana Logo - // FIXME: comment author should be written by Grafana and not the user - return r.gh.CreatePullRequestFileComment(ctx, r.owner, r.repo, prNumber, fileComment) -} - // ResourceURLs implements RepositoryWithURLs. func (r *githubRepository) ResourceURLs(ctx context.Context, file *FileInfo) (*provisioning.ResourceURLs, error) { cfg := r.config.Spec.GitHub diff --git a/pkg/registry/apis/provisioning/repository/github/client.go b/pkg/registry/apis/provisioning/repository/github/client.go index 943057463cc..9c35ca3898a 100644 --- a/pkg/registry/apis/provisioning/repository/github/client.go +++ b/pkg/registry/apis/provisioning/repository/github/client.go @@ -100,8 +100,6 @@ type Client interface { ListPullRequestFiles(ctx context.Context, owner, repository string, number int) ([]CommitFile, error) CreatePullRequestComment(ctx context.Context, owner, repository string, number int, body string) error - CreatePullRequestFileComment(ctx context.Context, owner, repository string, number int, comment FileComment) error - ClearAllPullRequestFileComments(ctx context.Context, owner, repository string, number int) error } //go:generate mockery --name RepositoryContent --structname MockRepositoryContent --inpackage --filename mock_repository_content.go --with-expecter diff --git a/pkg/registry/apis/provisioning/repository/github/impl.go b/pkg/registry/apis/provisioning/repository/github/impl.go index 9ad02c6edc8..d94f2a544cb 100644 --- a/pkg/registry/apis/provisioning/repository/github/impl.go +++ b/pkg/registry/apis/provisioning/repository/github/impl.go @@ -18,9 +18,7 @@ type githubClient struct { gh *github.Client } -var _ Client = (*githubClient)(nil) - -func NewClient(client *github.Client) *githubClient { +func NewClient(client *github.Client) Client { return &githubClient{client} } @@ -141,6 +139,7 @@ func (r *githubClient) GetTree(ctx context.Context, owner, repository, basePath, if currentRef != ref { // We're operating with a subpath which doesn't exist yet. // Pretend as if there is simply no files. + // FIXME: why should we pretend this? return nil, false, nil } // currentRef == ref @@ -309,6 +308,8 @@ func (r *githubClient) Commits(ctx context.Context, owner, repository, path, bra ret := make([]Commit, 0, len(commits)) for _, c := range commits { + // FIXME: This code is a mess. I am pretty sure that we have issue in + // some situations var createdAt time.Time var author *CommitAuthor if c.GetCommit().GetAuthor() != nil { @@ -373,12 +374,22 @@ func (r *githubClient) CompareCommits(ctx context.Context, owner, repository, ba } func (r *githubClient) GetBranch(ctx context.Context, owner, repository, branchName string) (Branch, error) { - branch, _, err := r.gh.Repositories.GetBranch(ctx, owner, repository, branchName, 0) + branch, resp, err := r.gh.Repositories.GetBranch(ctx, owner, repository, branchName, 0) if err != nil { + // For some reason, GitHub client handles this case differently by failing with a wrapped error + if resp != nil && resp.StatusCode == http.StatusNotFound { + return Branch{}, ErrResourceNotFound + } + + if resp != nil && resp.StatusCode == http.StatusServiceUnavailable { + return Branch{}, ErrServiceUnavailable + } + var ghErr *github.ErrorResponse if !errors.As(err, &ghErr) { return Branch{}, err } + // Leaving these just in case if ghErr.Response.StatusCode == http.StatusServiceUnavailable { return Branch{}, ErrServiceUnavailable } @@ -520,6 +531,8 @@ func (r *githubClient) GetWebhook(ctx context.Context, owner, repository string, contentType := hook.GetConfig().GetContentType() if contentType == "" { + // FIXME: Not sure about the value of the contentType + // we default to form in the other ones but to JSON here contentType = "json" } @@ -613,60 +626,6 @@ func (r *githubClient) CreatePullRequestComment(ctx context.Context, owner, repo return nil } -func (r *githubClient) CreatePullRequestFileComment(ctx context.Context, owner, repository string, number int, comment FileComment) error { - commentRequest := &github.PullRequestComment{ - Body: &comment.Content, - CommitID: &comment.Ref, - Path: &comment.Path, - Position: &comment.Position, - } - - if _, _, err := r.gh.PullRequests.CreateComment(ctx, owner, repository, number, commentRequest); err != nil { - var ghErr *github.ErrorResponse - if errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusServiceUnavailable { - return ErrServiceUnavailable - } - - return err - } - - return nil -} - -func (r *githubClient) ClearAllPullRequestFileComments(ctx context.Context, owner, repository string, number int) error { - listFn := func(ctx context.Context, opts *github.ListOptions) ([]*github.PullRequestComment, *github.Response, error) { - return r.gh.PullRequests.ListComments(ctx, owner, repository, number, &github.PullRequestListCommentsOptions{ - ListOptions: *opts, - }) - } - - comments, err := paginatedList(ctx, listFn, defaultListOptions(maxPullRequestsFileComments)) - if errors.Is(err, ErrTooManyItems) { - return fmt.Errorf("too many comments to process (more than %d)", maxPullRequestsFileComments) - } - if err != nil { - return err - } - - userLogin, _, err := r.gh.Users.Get(ctx, "") - if err != nil { - return fmt.Errorf("get user: %w", err) - } - - for _, c := range comments { - // skip if comments were not created by us - if c.User.GetLogin() != userLogin.GetLogin() { - continue - } - - if _, err := r.gh.PullRequests.DeleteComment(ctx, owner, repository, c.GetID()); err != nil { - return fmt.Errorf("delete comment: %w", err) - } - } - - return nil -} - type realRepositoryContent struct { real *github.RepositoryContent } diff --git a/pkg/registry/apis/provisioning/repository/github/impl_test.go b/pkg/registry/apis/provisioning/repository/github/impl_test.go new file mode 100644 index 00000000000..b979f288836 --- /dev/null +++ b/pkg/registry/apis/provisioning/repository/github/impl_test.go @@ -0,0 +1,3859 @@ +package github + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strings" + "testing" + "time" + + "github.com/google/go-github/v70/github" + mockhub "github.com/migueleliasweb/go-github-mock/src/mock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + apierrors "k8s.io/apimachinery/pkg/api/errors" +) + +func TestIsAuthenticated(t *testing.T) { + tests := []struct { + name string + mockHandler *http.Client + wantErr error + }{ + { + name: "successful authentication", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatch( + mockhub.GetUser, + github.User{}, + ), + ), + wantErr: nil, + }, + { + name: "unauthorized - invalid token", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetUser, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"message": "Bad credentials"})) + }), + ), + ), + wantErr: apierrors.NewUnauthorized("token is invalid or expired"), + }, + { + name: "forbidden - insufficient permissions", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetUser, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusForbidden) + require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"message": "Forbidden"})) + }), + ), + ), + wantErr: apierrors.NewUnauthorized("token is revoked or has insufficient permissions"), + }, + { + name: "service unavailable", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetUser, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"message": "Service unavailable"})) + }), + ), + ), + wantErr: ErrServiceUnavailable, + }, + { + name: "unknown error", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetUser, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"message": "Internal server error"})) + }), + ), + ), + wantErr: errors.New("500 Internal server error []"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a mock client + factory := ProvideFactory() + factory.Client = tt.mockHandler + client := factory.New(context.Background(), "") + + // Call the method being tested + err := client.IsAuthenticated(context.Background()) + // Check the error + if tt.wantErr == nil { + assert.NoError(t, err) + } else { + assert.Error(t, err) + var statusErr *apierrors.StatusError + if errors.As(tt.wantErr, &statusErr) { + // For StatusError, compare status code + var actualStatusErr *apierrors.StatusError + assert.True(t, errors.As(err, &actualStatusErr), "Expected StatusError but got different error type") + if actualStatusErr != nil { + assert.Equal(t, statusErr.Status().Code, actualStatusErr.Status().Code) + assert.Equal(t, statusErr.Status().Message, actualStatusErr.Status().Message) + } + } else { + // For regular errors, compare error messages + assert.Contains(t, err.Error(), tt.wantErr.Error()) + } + } + }) + } +} +func TestGithubClient_RepoExists(t *testing.T) { + tests := []struct { + name string + mockHandler *http.Client + owner string + repository string + want bool + wantErr error + }{ + { + name: "repository exists", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(map[string]interface{}{ + "id": 123, + "name": "test-repo", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + want: true, + wantErr: nil, + }, + { + name: "repository does not exist", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"message": "Not Found"})) + }), + ), + ), + owner: "test-owner", + repository: "non-existent-repo", + want: false, + wantErr: nil, + }, + { + name: "service unavailable", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"message": "Service unavailable"})) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + want: false, + wantErr: errors.New("503 Service unavailable []"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a mock client + factory := ProvideFactory() + factory.Client = tt.mockHandler + client := factory.New(context.Background(), "") + + // Call the method being tested + exists, err := client.RepoExists(context.Background(), tt.owner, tt.repository) + + // Check the result + assert.Equal(t, tt.want, exists) + + // Check the error + if tt.wantErr == nil { + assert.NoError(t, err) + } else { + assert.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr.Error()) + } + }) + } +} + +func TestGithubClient_GetContents(t *testing.T) { + tests := []struct { + name string + mockHandler *http.Client + owner string + repository string + path string + ref string + wantFile bool + wantDir bool + wantErr error + }{ + { + name: "get file contents", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + fileContent := &github.RepositoryContent{ + Type: github.Ptr("file"), + Name: github.Ptr("test.txt"), + Path: github.Ptr("test.txt"), + Content: github.Ptr("dGVzdCBjb250ZW50"), // base64 encoded "test content" + Encoding: github.Ptr("base64"), + Size: github.Ptr(12), + SHA: github.Ptr("abc123"), + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(fileContent)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + path: "test.txt", + ref: "main", + wantFile: true, + wantDir: false, + wantErr: nil, + }, + { + name: "get directory contents", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + dirContents := []*github.RepositoryContent{ + { + Type: github.Ptr("file"), + Name: github.Ptr("file1.txt"), + Path: github.Ptr("dir/file1.txt"), + Size: github.Ptr(100), + SHA: github.Ptr("abc123"), + }, + { + Type: github.Ptr("dir"), + Name: github.Ptr("subdir"), + Path: github.Ptr("dir/subdir"), + }, + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(dirContents)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + path: "dir", + ref: "main", + wantFile: false, + wantDir: true, + wantErr: nil, + }, + { + name: "resource not found", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"message": "Not Found"})) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + path: "nonexistent.txt", + ref: "main", + wantFile: false, + wantDir: false, + wantErr: ErrResourceNotFound, + }, + { + name: "service unavailable", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"message": "Service unavailable"})) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + path: "test.txt", + ref: "main", + wantFile: false, + wantDir: false, + wantErr: ErrServiceUnavailable, + }, + { + name: "file too large", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + fileContent := &github.RepositoryContent{ + Type: github.Ptr("file"), + Name: github.Ptr("large.txt"), + Path: github.Ptr("large.txt"), + Content: github.Ptr(""), + Encoding: github.Ptr("base64"), + Size: github.Ptr(maxFileSize + 1), // Exceeds max file size + SHA: github.Ptr("abc123"), + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(fileContent)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + path: "large.txt", + ref: "main", + wantFile: false, + wantDir: false, + wantErr: ErrFileTooLarge, + }, + { + name: "not a github error response", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusConflict) + // Return a non-GitHub error format + _, err := w.Write([]byte("not a github error")) + require.NoError(t, err) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + path: "test.txt", + ref: "main", + wantFile: false, + wantDir: false, + wantErr: errors.New("409"), + }, + { + name: "directory with too many items", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + // Create a directory with more than maxDirectoryItems + dirContents := make([]*github.RepositoryContent, maxDirectoryItems+1) + for i := 0; i < maxDirectoryItems+1; i++ { + dirContents[i] = &github.RepositoryContent{ + Type: github.Ptr("file"), + Name: github.Ptr(fmt.Sprintf("file%d.txt", i)), + Path: github.Ptr(fmt.Sprintf("dir/file%d.txt", i)), + Size: github.Ptr(100), + SHA: github.Ptr(fmt.Sprintf("sha%d", i)), + } + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(dirContents)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + path: "dir", + ref: "main", + wantFile: false, + wantDir: false, + wantErr: fmt.Errorf("directory contains too many items (more than %d)", maxDirectoryItems), + }, + { + name: "error response with other status code", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusForbidden) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusForbidden, + }, + Message: "Forbidden access", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + path: "test.txt", + ref: "main", + wantFile: false, + wantDir: false, + wantErr: errors.New("Forbidden access"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a mock client + factory := ProvideFactory() + factory.Client = tt.mockHandler + client := factory.New(context.Background(), "") + + // Call the method being tested + fileContent, dirContents, err := client.GetContents(context.Background(), tt.owner, tt.repository, tt.path, tt.ref) + + // Check the error + if tt.wantErr != nil { + assert.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr.Error()) + assert.Nil(t, fileContent) + assert.Nil(t, dirContents) + return + } + assert.NoError(t, err) + + // Check the result + if tt.wantFile { + assert.NotNil(t, fileContent) + assert.Nil(t, dirContents) + } else if tt.wantDir { + assert.Nil(t, fileContent) + assert.NotNil(t, dirContents) + assert.Greater(t, len(dirContents), 0) + } + }) + } +} + +func TestGithubClient_GetTree(t *testing.T) { + tests := []struct { + name string + mockHandler *http.Client + owner string + repository string + basePath string + ref string + recursive bool + wantItems int + wantTrunc bool + wantErr error + }{ + { + name: "get tree successfully", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposGitTreesByOwnerByRepoByTreeSha, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + tree := &github.Tree{ + SHA: github.Ptr("abc123"), + Entries: []*github.TreeEntry{ + { + Path: github.Ptr("file1.txt"), + Mode: github.Ptr("100644"), + Type: github.Ptr("blob"), + Size: github.Ptr(12), + SHA: github.Ptr("file1sha"), + }, + { + Path: github.Ptr("file2.txt"), + Mode: github.Ptr("100644"), + Type: github.Ptr("blob"), + Size: github.Ptr(14), + SHA: github.Ptr("file2sha"), + }, + { + Path: github.Ptr("dir"), + Mode: github.Ptr("040000"), + Type: github.Ptr("tree"), + SHA: github.Ptr("dirsha"), + }, + }, + Truncated: github.Ptr(false), + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(tree)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + basePath: "", + ref: "main", + recursive: false, + wantItems: 3, + wantTrunc: false, + wantErr: nil, + }, + { + name: "get tree with subpath", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposGitTreesByOwnerByRepoByTreeSha, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Check if this is the first request for the root tree + if !strings.Contains(r.URL.Path, "subdirsha") { + // Verify the request URL contains the correct owner, repo, and ref + expectedPath := "/repos/test-owner/test-repo/git/trees/main" + assert.True(t, strings.Contains(r.URL.Path, expectedPath), + "Expected URL path to contain %s, got %s", expectedPath, r.URL.Path) + + // Verify query parameters for recursive flag + query := r.URL.Query() + assert.Equal(t, "", query.Get("recursive"), "Recursive parameter should not be set") + } else { + // This is the second request for the subtree + assert.True(t, strings.Contains(r.URL.Path, "subdirsha"), + "Expected URL path to contain subdirsha, got %s", r.URL.Path) + } + // First request for the root tree + tree := &github.Tree{ + SHA: github.Ptr("rootsha"), + Entries: []*github.TreeEntry{ + { + Path: github.Ptr("subdir"), + Mode: github.Ptr("040000"), + Type: github.Ptr("tree"), + SHA: github.Ptr("subdirsha"), + }, + }, + Truncated: github.Ptr(false), + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(tree)) + }), + ), + mockhub.WithRequestMatchHandler( + mockhub.GetReposGitTreesByOwnerByRepoByTreeSha, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Second request for the subdir tree + if strings.Contains(r.URL.Path, "subdirsha") { + tree := &github.Tree{ + SHA: github.Ptr("subdirsha"), + Entries: []*github.TreeEntry{ + { + Path: github.Ptr("file3.txt"), + Mode: github.Ptr("100644"), + Type: github.Ptr("blob"), + Size: github.Ptr(16), + SHA: github.Ptr("file3sha"), + }, + }, + Truncated: github.Ptr(false), + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(tree)) + } + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + basePath: "subdir", + ref: "main", + recursive: false, + wantItems: 1, + wantTrunc: false, + wantErr: nil, + }, + { + name: "subpath not found should pretend is empty", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposGitTreesByOwnerByRepoByTreeSha, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // First request for the root tree + if !strings.Contains(r.URL.Path, "nonexistentsha") { + tree := &github.Tree{ + SHA: github.Ptr("rootsha"), + Entries: []*github.TreeEntry{ + { + Path: github.Ptr("nonexistent"), + Mode: github.Ptr("040000"), + Type: github.Ptr("tree"), + SHA: github.Ptr("nonexistentsha"), + }, + }, + Truncated: github.Ptr(false), + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(tree)) + } else { + // Second request for the nonexistent subtree returns 404 + w.WriteHeader(http.StatusNotFound) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusNotFound, + }, + Message: "Not Found", + })) + } + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + basePath: "nonexistent", + ref: "main", + recursive: false, + wantItems: 0, + wantTrunc: false, + wantErr: nil, + }, + { + name: "get tree fails with service unavailable", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposGitTreesByOwnerByRepoByTreeSha, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusServiceUnavailable, + }, + Message: "Service unavailable", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + basePath: "", + ref: "main", + recursive: false, + wantItems: 0, + wantTrunc: false, + wantErr: ErrServiceUnavailable, + }, + { + name: "tree contains too many items", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposGitTreesByOwnerByRepoByTreeSha, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + // Create more entries than the maxTreeItems limit + entries := make([]*github.TreeEntry, maxTreeItems+1) + for i := 0; i < maxTreeItems+1; i++ { + entries[i] = &github.TreeEntry{ + Path: github.Ptr(fmt.Sprintf("file%d.txt", i+1)), + Mode: github.Ptr("100644"), + Type: github.Ptr("blob"), + Size: github.Ptr(12), + SHA: github.Ptr(fmt.Sprintf("sha%d", i+1)), + } + } + + tree := &github.Tree{ + SHA: github.Ptr("abc123"), + Entries: entries, + Truncated: github.Ptr(false), + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(tree)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + basePath: "", + ref: "main", + recursive: false, + wantItems: 0, + wantTrunc: false, + wantErr: fmt.Errorf("tree contains too many items (more than %d)", maxTreeItems), + }, + + { + name: "tree is truncated with recursive mode", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposGitTreesByOwnerByRepoByTreeSha, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + tree := &github.Tree{ + SHA: github.Ptr("abc123"), + Entries: []*github.TreeEntry{ + { + Path: github.Ptr("file1.txt"), + Mode: github.Ptr("100644"), + Type: github.Ptr("blob"), + Size: github.Ptr(12), + SHA: github.Ptr("file1sha"), + }, + }, + Truncated: github.Ptr(true), + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(tree)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + basePath: "", + ref: "main", + recursive: true, + wantItems: 0, + wantTrunc: true, + wantErr: fmt.Errorf("tree is too large to fetch recursively (more than 10000 items)"), + }, + { + name: "repository not found", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposGitTreesByOwnerByRepoByTreeSha, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusNotFound, + }, + Message: "Not Found", + })) + }), + ), + ), + owner: "test-owner", + repository: "non-existent-repo", + basePath: "", + ref: "main", + recursive: false, + wantItems: 0, + wantTrunc: false, + wantErr: ErrResourceNotFound, + }, + { + name: "service unavailable", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposGitTreesByOwnerByRepoByTreeSha, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusServiceUnavailable, + }, + Message: "Service unavailable", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + basePath: "", + ref: "main", + recursive: false, + wantItems: 0, + wantTrunc: false, + wantErr: ErrServiceUnavailable, + }, + { + name: "too many items in tree", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposGitTreesByOwnerByRepoByTreeSha, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + // Create a tree with more than maxTreeItems entries + entries := make([]*github.TreeEntry, maxTreeItems+1) + for i := 0; i < maxTreeItems+1; i++ { + entries[i] = &github.TreeEntry{ + Path: github.Ptr(fmt.Sprintf("file%d.txt", i)), + Mode: github.Ptr("100644"), + Type: github.Ptr("blob"), + Size: github.Ptr(12), + SHA: github.Ptr(fmt.Sprintf("sha%d", i)), + } + } + tree := &github.Tree{ + SHA: github.Ptr("abc123"), + Entries: entries, + Truncated: github.Ptr(false), + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(tree)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + basePath: "", + ref: "main", + recursive: false, + wantItems: 0, + wantTrunc: false, + wantErr: fmt.Errorf("tree contains too many items (more than 10000)"), + }, + { + name: "folder not found in tree", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposGitTreesByOwnerByRepoByTreeSha, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + // Return a tree that doesn't contain the requested folder + tree := &github.Tree{ + SHA: github.Ptr("rootsha"), + Entries: []*github.TreeEntry{ + { + Path: github.Ptr("other-folder"), + Mode: github.Ptr("040000"), + Type: github.Ptr("tree"), + SHA: github.Ptr("othersha"), + }, + { + Path: github.Ptr("file.txt"), + Mode: github.Ptr("100644"), + Type: github.Ptr("blob"), + Size: github.Ptr(12), + SHA: github.Ptr("filesha"), + }, + }, + Truncated: github.Ptr(false), + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(tree)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + basePath: "non-existent-folder/subpath", + ref: "main", + recursive: false, + wantItems: 0, + wantTrunc: false, + wantErr: nil, + }, + { + name: "non-standard error is passed through", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposGitTreesByOwnerByRepoByTreeSha, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusForbidden) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusForbidden, + }, + Message: "Forbidden access", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + basePath: "", + ref: "main", + recursive: false, + wantItems: 0, + wantTrunc: false, + wantErr: errors.New("Forbidden access"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a mock client + factory := ProvideFactory() + factory.Client = tt.mockHandler + client := factory.New(context.Background(), "") + + // Call the method being tested + contents, truncated, err := client.GetTree(context.Background(), tt.owner, tt.repository, tt.basePath, tt.ref, tt.recursive) + + // Check the error + if tt.wantErr != nil { + assert.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr.Error()) + assert.Nil(t, contents) + return + } + assert.NoError(t, err) + + // Check truncated flag + assert.Equal(t, tt.wantTrunc, truncated) + + // Check the result + if tt.wantItems > 0 { + assert.NotNil(t, contents) + assert.Equal(t, tt.wantItems, len(contents)) + } else { + assert.Empty(t, contents) + } + }) + } +} + +func TestGithubClient_CreateFile(t *testing.T) { + tests := []struct { + name string + mockHandler *http.Client + owner string + repository string + path string + branch string + message string + content []byte + wantErr error + }{ + { + name: "create file successfully", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.PutReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + response := &github.RepositoryContentResponse{ + Content: &github.RepositoryContent{ + Name: github.Ptr("test.txt"), + Path: github.Ptr("test.txt"), + SHA: github.Ptr("abc123"), + }, + } + w.WriteHeader(http.StatusCreated) + require.NoError(t, json.NewEncoder(w).Encode(response)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + path: "test.txt", + branch: "main", + message: "Add test.txt", + content: []byte("test content"), + wantErr: nil, + }, + { + name: "file already exists", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.PutReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnprocessableEntity) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusUnprocessableEntity, + }, + Message: "File already exists", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + path: "existing.txt", + branch: "main", + message: "Add existing.txt", + content: []byte("test content"), + wantErr: ErrResourceAlreadyExists, + }, + { + name: "service unavailable", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.PutReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusServiceUnavailable, + }, + Message: "Service unavailable", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + path: "test.txt", + branch: "main", + message: "Add test.txt", + content: []byte("test content"), + wantErr: errors.New("Service unavailable"), + }, + { + name: "not a github error response", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.PutReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + _, err := w.Write([]byte("not a github error")) + require.NoError(t, err) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + path: "test.txt", + branch: "main", + message: "Add test.txt", + content: []byte("test content"), + wantErr: errors.New("500"), + }, + { + name: "default commit message", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.PutReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Decode the request to verify the message + body, err := io.ReadAll(r.Body) + require.NoError(t, err) + + var reqData struct { + Message string `json:"message"` + } + require.NoError(t, json.Unmarshal(body, &reqData)) + assert.Equal(t, "Create test.txt", reqData.Message) + + response := &github.RepositoryContentResponse{ + Content: &github.RepositoryContent{ + Name: github.Ptr("test.txt"), + Path: github.Ptr("test.txt"), + SHA: github.Ptr("abc123"), + }, + } + w.WriteHeader(http.StatusCreated) + require.NoError(t, json.NewEncoder(w).Encode(response)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + path: "test.txt", + branch: "main", + message: "", // Empty message should use default + content: []byte("test content"), + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a mock client + factory := ProvideFactory() + factory.Client = tt.mockHandler + client := factory.New(context.Background(), "") + + // Call the method being tested + err := client.CreateFile(context.Background(), tt.owner, tt.repository, tt.path, tt.branch, tt.message, tt.content) + + // Check the error + if tt.wantErr != nil { + assert.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestUpdateFile(t *testing.T) { + tests := []struct { + name string + mockHandler *http.Client + owner string + repository string + path string + branch string + message string + hash string + content []byte + wantErr error + }{ + { + name: "successful update", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.PutReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Verify request body + body, err := io.ReadAll(r.Body) + require.NoError(t, err) + + var reqData struct { + Message string `json:"message"` + SHA string `json:"sha"` + } + require.NoError(t, json.Unmarshal(body, &reqData)) + assert.Equal(t, "Update test.txt", reqData.Message) + assert.Equal(t, "abc123", reqData.SHA) + + response := &github.RepositoryContentResponse{ + Content: &github.RepositoryContent{ + Name: github.Ptr("test.txt"), + Path: github.Ptr("test.txt"), + SHA: github.Ptr("def456"), + }, + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(response)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + path: "test.txt", + branch: "main", + message: "", // Empty message should use default + hash: "abc123", + content: []byte("updated content"), + wantErr: nil, + }, + { + name: "file not found", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.PutReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"message": "Not Found"})) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + path: "nonexistent.txt", + branch: "main", + message: "Update nonexistent file", + hash: "abc123", + content: []byte("content"), + wantErr: ErrResourceNotFound, + }, + { + name: "mismatched hash", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.PutReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusConflict) + require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"message": "SHA mismatch"})) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + path: "test.txt", + branch: "main", + message: "Update with wrong hash", + hash: "wrong-hash", + content: []byte("content"), + wantErr: ErrMismatchedHash, + }, + { + name: "service unavailable", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.PutReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"message": "Service unavailable"})) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + path: "test.txt", + branch: "main", + message: "Update during outage", + hash: "abc123", + content: []byte("content"), + wantErr: ErrServiceUnavailable, + }, + { + name: "other error", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.PutReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"message": "Internal server error"})) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + path: "test.txt", + branch: "main", + message: "Update with server error", + hash: "abc123", + content: []byte("content"), + wantErr: errors.New("Internal server error"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a mock client + factory := ProvideFactory() + factory.Client = tt.mockHandler + client := factory.New(context.Background(), "") + + // Call the method being tested + err := client.UpdateFile(context.Background(), tt.owner, tt.repository, tt.path, tt.branch, tt.message, tt.hash, tt.content) + + // Check the error + if tt.wantErr != nil { + assert.Error(t, err) + if errors.Is(err, tt.wantErr) { + assert.Equal(t, tt.wantErr, err) + } else { + assert.Contains(t, err.Error(), tt.wantErr.Error()) + } + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestGithubClient_DeleteFile(t *testing.T) { + tests := []struct { + name string + mockHandler *http.Client + owner string + repository string + path string + branch string + message string + hash string + wantErr error + }{ + { + name: "delete file successfully", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.DeleteReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + response := &github.RepositoryContentResponse{ + Content: nil, + Commit: github.Commit{ + SHA: github.Ptr("def456"), + }, + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(response)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + path: "test.txt", + branch: "main", + message: "Delete test.txt", + hash: "abc123", + wantErr: nil, + }, + { + name: "file not found", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.DeleteReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusNotFound, + }, + Message: "Not Found", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + path: "nonexistent.txt", + branch: "main", + message: "Delete nonexistent.txt", + hash: "abc123", + wantErr: ErrResourceNotFound, + }, + { + name: "mismatched hash", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.DeleteReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusConflict) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusConflict, + }, + Message: "Conflict", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + path: "test.txt", + branch: "main", + message: "Delete test.txt", + hash: "wrong-hash", + wantErr: ErrMismatchedHash, + }, + { + name: "service unavailable", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.DeleteReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusServiceUnavailable, + }, + Message: "Service unavailable", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + path: "test.txt", + branch: "main", + message: "Delete test.txt", + hash: "abc123", + wantErr: ErrServiceUnavailable, + }, + { + name: "other error", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.DeleteReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"message": "Internal server error"})) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + path: "test.txt", + branch: "main", + message: "Delete with server error", + hash: "abc123", + wantErr: errors.New("Internal server error"), + }, + { + name: "default commit message", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.DeleteReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Decode the request to verify the message + body, err := io.ReadAll(r.Body) + require.NoError(t, err) + + var reqData struct { + Message string `json:"message"` + } + require.NoError(t, json.Unmarshal(body, &reqData)) + assert.Equal(t, "Delete test.txt", reqData.Message) + + response := &github.RepositoryContentResponse{ + Content: nil, + Commit: github.Commit{ + SHA: github.Ptr("def456"), + }, + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(response)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + path: "test.txt", + branch: "main", + message: "", + hash: "abc123", + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a mock client + factory := ProvideFactory() + factory.Client = tt.mockHandler + client := factory.New(context.Background(), "") + + // Call the method being tested + err := client.DeleteFile(context.Background(), tt.owner, tt.repository, tt.path, tt.branch, tt.message, tt.hash) + + // Check the error + if tt.wantErr != nil { + assert.Error(t, err) + if errors.Is(err, tt.wantErr) { + assert.Equal(t, tt.wantErr, err) + } else { + assert.Contains(t, err.Error(), tt.wantErr.Error()) + } + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestGithubClient_GetCommits(t *testing.T) { + tests := []struct { + name string + mockHandler *http.Client + owner string + repository string + branch string + path string + since time.Time + until time.Time + wantCommits []Commit + wantErr error + }{ + { + name: "get commits successfully", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposCommitsByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + commits := []*github.RepositoryCommit{ + { + SHA: github.Ptr("abc123"), + Commit: &github.Commit{ + Message: github.Ptr("First commit"), + Author: &github.CommitAuthor{ + Name: github.Ptr("Test User"), + Email: github.Ptr("test@example.com"), + Date: &github.Timestamp{Time: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)}, + }, + Committer: &github.CommitAuthor{ + Name: github.Ptr("Test User"), + Email: github.Ptr("test@example.com"), + Date: &github.Timestamp{Time: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)}, + }, + }, + Author: &github.User{ + Login: github.Ptr("test-user"), + AvatarURL: github.Ptr("https://avatar.url"), + }, + Committer: &github.User{ + Login: github.Ptr("test-user"), + AvatarURL: github.Ptr("https://avatar.url"), + }, + }, + { + SHA: github.Ptr("def456"), + Commit: &github.Commit{ + Message: github.Ptr("Second commit"), + Author: &github.CommitAuthor{ + Name: github.Ptr("Another User"), + Email: github.Ptr("another@example.com"), + Date: &github.Timestamp{Time: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC)}, + }, + Committer: &github.CommitAuthor{ + Name: github.Ptr("Another User"), + Email: github.Ptr("another@example.com"), + Date: &github.Timestamp{Time: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC)}, + }, + }, + Author: &github.User{ + Login: github.Ptr("another-user"), + AvatarURL: github.Ptr("https://another.avatar.url"), + }, + Committer: &github.User{ + Login: github.Ptr("another-user"), + AvatarURL: github.Ptr("https://another.avatar.url"), + }, + }, + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(commits)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + branch: "main", + path: "test.txt", + since: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + until: time.Date(2023, 1, 3, 0, 0, 0, 0, time.UTC), + wantCommits: []Commit{ + { + Ref: "abc123", + Message: "First commit", + Author: &CommitAuthor{ + Name: "Test User", + Username: "test-user", + AvatarURL: "https://avatar.url", + }, + Committer: &CommitAuthor{ + Name: "Test User", + Username: "test-user", + AvatarURL: "https://avatar.url", + }, + CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), + }, + { + Ref: "def456", + Message: "Second commit", + Author: &CommitAuthor{ + Name: "Another User", + Username: "another-user", + AvatarURL: "https://another.avatar.url", + }, + Committer: &CommitAuthor{ + Name: "Another User", + Username: "another-user", + AvatarURL: "https://another.avatar.url", + }, + CreatedAt: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC), + }, + }, + wantErr: nil, + }, + { + name: "repository not found", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposCommitsByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusNotFound, + }, + Message: "Not Found", + })) + }), + ), + ), + owner: "test-owner", + repository: "nonexistent-repo", + branch: "main", + path: "test.txt", + since: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + until: time.Date(2023, 1, 3, 0, 0, 0, 0, time.UTC), + wantCommits: nil, + wantErr: ErrResourceNotFound, + }, + { + name: "commits missing author", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposCommitsByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + commits := []*github.RepositoryCommit{ + { + SHA: github.Ptr("abc123"), + Commit: &github.Commit{ + Message: github.Ptr("First commit"), + Author: &github.CommitAuthor{ + Name: github.Ptr("Test User"), + Date: &github.Timestamp{Time: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)}, + Email: github.Ptr("test@example.com"), + }, + Committer: &github.CommitAuthor{ + Name: github.Ptr("Test User"), + Date: &github.Timestamp{Time: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)}, + Email: github.Ptr("test@example.com"), + }, + }, + // Author is nil + Committer: &github.User{ + Login: github.Ptr("test-user"), + AvatarURL: github.Ptr("https://avatar.url"), + }, + }, + { + SHA: github.Ptr("def456"), + Commit: &github.Commit{ + Message: github.Ptr("Second commit"), + // Missing Author in Commit + Committer: &github.CommitAuthor{ + Name: github.Ptr("Another User"), + Date: &github.Timestamp{Time: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC)}, + Email: github.Ptr("another@example.com"), + }, + }, + Author: &github.User{ + Login: github.Ptr("another-user"), + AvatarURL: github.Ptr("https://another.avatar.url"), + }, + Committer: &github.User{ + Login: github.Ptr("another-user"), + AvatarURL: github.Ptr("https://another.avatar.url"), + }, + }, + } + require.NoError(t, json.NewEncoder(w).Encode(commits)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + branch: "main", + path: "test.txt", + since: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + until: time.Date(2023, 1, 3, 0, 0, 0, 0, time.UTC), + wantCommits: []Commit{ + { + Ref: "abc123", + Message: "First commit", + Author: &CommitAuthor{ + Name: "Test User", + // Username and AvatarURL are empty because Author is nil in the response + }, + Committer: &CommitAuthor{ + Name: "Test User", + Username: "test-user", + AvatarURL: "https://avatar.url", + }, + CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC), + }, + { + Ref: "def456", + Message: "Second commit", + Author: nil, // Author is nil because Commit.Author is nil in the response + Committer: &CommitAuthor{ + Name: "Another User", + Username: "another-user", + AvatarURL: "https://another.avatar.url", + }, + }, + }, + wantErr: nil, + }, + { + name: "too many commits", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposCommitsByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + // Return a large number of commits that would exceed the maxCommits limit + commits := make([]*github.RepositoryCommit, maxCommits+1) + for i := 0; i < maxCommits+1; i++ { + commits[i] = &github.RepositoryCommit{ + SHA: github.Ptr(fmt.Sprintf("commit%d", i)), + Commit: &github.Commit{ + Message: github.Ptr(fmt.Sprintf("Commit %d", i)), + Author: &github.CommitAuthor{ + Name: github.Ptr("Test User"), + Date: &github.Timestamp{Time: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)}, + Email: github.Ptr("test@example.com"), + }, + }, + Author: &github.User{ + Login: github.Ptr("test-user"), + AvatarURL: github.Ptr("https://avatar.url"), + }, + } + } + require.NoError(t, json.NewEncoder(w).Encode(commits)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + branch: "main", + path: "test.txt", + since: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + until: time.Date(2023, 1, 3, 0, 0, 0, 0, time.UTC), + wantCommits: nil, + wantErr: fmt.Errorf("too many commits to fetch (more than %d)", maxCommits), + }, + { + name: "service unavailable", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposCommitsByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusServiceUnavailable, + }, + Message: "Service unavailable", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + branch: "main", + path: "test.txt", + since: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + until: time.Date(2023, 1, 3, 0, 0, 0, 0, time.UTC), + wantCommits: nil, + wantErr: ErrServiceUnavailable, + }, + { + name: "other error", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposCommitsByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusInternalServerError, + }, + Message: "Internal server error", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + branch: "main", + path: "test.txt", + since: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + until: time.Date(2023, 1, 3, 0, 0, 0, 0, time.UTC), + wantCommits: nil, + wantErr: errors.New("Internal server error"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a mock client + factory := ProvideFactory() + factory.Client = tt.mockHandler + client := factory.New(context.Background(), "") + + // Call the method being tested + commits, err := client.Commits(context.Background(), tt.owner, tt.repository, tt.branch, tt.path) + + // Check the error + if tt.wantErr != nil { + assert.Error(t, err) + if errors.Is(err, tt.wantErr) { + assert.Equal(t, tt.wantErr, err) + } else { + assert.Contains(t, err.Error(), tt.wantErr.Error()) + } + assert.Nil(t, commits) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.wantCommits, commits) + } + }) + } +} + +func TestCompareCommits(t *testing.T) { + tests := []struct { + name string + mockHandler *http.Client + owner string + repository string + base string + head string + wantFiles []CommitFile + wantErr error + }{ + { + name: "successful comparison", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposCompareByOwnerByRepoByBasehead, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + files := []*github.CommitFile{ + { + Filename: github.Ptr("file1.txt"), + Status: github.Ptr("modified"), + Additions: github.Ptr(10), + Deletions: github.Ptr(5), + Changes: github.Ptr(15), + }, + { + Filename: github.Ptr("file2.txt"), + Status: github.Ptr("added"), + Additions: github.Ptr(20), + Deletions: github.Ptr(0), + Changes: github.Ptr(20), + }, + } + + require.NoError(t, json.NewEncoder(w).Encode(github.CommitsComparison{ + Files: files, + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + base: "main", + head: "feature-branch", + wantFiles: []CommitFile{ + &github.CommitFile{ + Filename: github.Ptr("file1.txt"), + Status: github.Ptr("modified"), + Additions: github.Ptr(10), + Deletions: github.Ptr(5), + Changes: github.Ptr(15), + }, + &github.CommitFile{ + Filename: github.Ptr("file2.txt"), + Status: github.Ptr("added"), + Additions: github.Ptr(20), + Deletions: github.Ptr(0), + Changes: github.Ptr(20), + }, + }, + wantErr: nil, + }, + { + name: "too many files", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposCompareByOwnerByRepoByBasehead, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + // Generate more files than the max limit + files := make([]*github.CommitFile, maxCompareFiles+1) + for i := 0; i < maxCompareFiles+1; i++ { + files[i] = &github.CommitFile{ + Filename: github.Ptr(fmt.Sprintf("file%d.txt", i)), + Status: github.Ptr("modified"), + } + } + + require.NoError(t, json.NewEncoder(w).Encode(github.CommitsComparison{ + Files: files, + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + base: "main", + head: "feature-branch", + wantFiles: nil, + wantErr: fmt.Errorf("too many files changed between commits (more than %d)", maxCompareFiles), + }, + { + name: "resource not found", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposCompareByOwnerByRepoByBasehead, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusNotFound, + }, + Message: "Not found", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + base: "main", + head: "feature-branch", + wantFiles: nil, + wantErr: ErrResourceNotFound, + }, + { + name: "service unavailable", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposCompareByOwnerByRepoByBasehead, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusServiceUnavailable, + }, + Message: "Service unavailable", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + base: "main", + head: "feature-branch", + wantFiles: nil, + wantErr: ErrServiceUnavailable, + }, + { + name: "other error", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposCompareByOwnerByRepoByBasehead, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusInternalServerError, + }, + Message: "Internal server error", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + base: "main", + head: "feature-branch", + wantFiles: nil, + wantErr: errors.New("Internal server error"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a mock client + factory := ProvideFactory() + factory.Client = tt.mockHandler + client := factory.New(context.Background(), "") + + // Call the method being tested + files, err := client.CompareCommits(context.Background(), tt.owner, tt.repository, tt.base, tt.head) + + // Check the error + if tt.wantErr != nil { + assert.Error(t, err) + if errors.Is(err, tt.wantErr) { + assert.Equal(t, tt.wantErr, err) + } else { + assert.Contains(t, err.Error(), tt.wantErr.Error()) + } + assert.Nil(t, files) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.wantFiles, files) + } + }) + } +} + +func TestGetBranch(t *testing.T) { + tests := []struct { + name string + mockHandler *http.Client + owner string + repository string + branchName string + wantBranch Branch + wantErr error + }{ + { + name: "get branch successfully", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposBranchesByOwnerByRepoByBranch, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + branch := &github.Branch{ + Name: github.Ptr("main"), + Commit: &github.RepositoryCommit{ + SHA: github.Ptr("abc123"), + }, + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(branch)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + branchName: "main", + wantBranch: Branch{ + Name: "main", + Sha: "abc123", + }, + wantErr: nil, + }, + { + name: "branch not found", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposBranchesByOwnerByRepoByBranch, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusNotFound, + }, + Message: "Branch not found", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + branchName: "non-existent", + wantBranch: Branch{}, + wantErr: ErrResourceNotFound, + }, + { + name: "service unavailable", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposBranchesByOwnerByRepoByBranch, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusServiceUnavailable, + }, + Message: "Service unavailable", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + branchName: "main", + wantBranch: Branch{}, + wantErr: ErrServiceUnavailable, + }, + { + name: "other error", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposBranchesByOwnerByRepoByBranch, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusInternalServerError, + }, + Message: "Internal server error", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + branchName: "main", + wantBranch: Branch{}, + wantErr: errors.New("unexpected status code: 500 Internal Server Error"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a mock client + factory := ProvideFactory() + factory.Client = tt.mockHandler + client := factory.New(context.Background(), "") + + // Call the method being tested + branch, err := client.GetBranch(context.Background(), tt.owner, tt.repository, tt.branchName) + + // Check the error + if tt.wantErr != nil { + assert.Error(t, err) + if errors.Is(err, tt.wantErr) { + assert.Equal(t, tt.wantErr, err) + } else { + assert.Contains(t, err.Error(), tt.wantErr.Error()) + } + assert.Equal(t, tt.wantBranch, branch) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.wantBranch, branch) + } + }) + } +} + +func TestGithubClient_CreateBranch(t *testing.T) { + tests := []struct { + name string + mockHandler *http.Client + owner string + repository string + sourceBranch string + branchName string + wantErr error + }{ + { + name: "successful branch creation", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposBranchesByOwnerByRepoByBranch, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // First call checks if branch exists (should return 404) + if strings.Contains(r.URL.Path, "/new-branch") { + w.WriteHeader(http.StatusNotFound) + return + } + + // Second call gets the source branch + if strings.Contains(r.URL.Path, "/main") { + branch := &github.Branch{ + Name: github.Ptr("main"), + Commit: &github.RepositoryCommit{ + SHA: github.Ptr("abc123"), + }, + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(branch)) + } + }), + ), + mockhub.WithRequestMatchHandler( + mockhub.PostReposGitRefsByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Verify the request body contains the correct reference + body, err := io.ReadAll(r.Body) + require.NoError(t, err) + ref := struct { + Ref string `json:"ref"` + SHA string `json:"sha"` + }{} + require.NoError(t, json.Unmarshal(body, &ref)) + assert.Equal(t, "refs/heads/new-branch", ref.Ref) + assert.Equal(t, "abc123", ref.SHA) + + w.WriteHeader(http.StatusCreated) + require.NoError(t, json.NewEncoder(w).Encode(&github.Reference{ + Ref: github.Ptr("refs/heads/new-branch"), + Object: &github.GitObject{ + SHA: github.Ptr("abc123"), + }, + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + sourceBranch: "main", + branchName: "new-branch", + wantErr: nil, + }, + { + name: "branch already exists", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposBranchesByOwnerByRepoByBranch, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Verify the request URL contains the correct owner, repo, and branch + expectedPath := "/repos/test-owner/test-repo/branches/existing-branch" + assert.True(t, strings.Contains(r.URL.Path, expectedPath), + "Expected URL path to contain %s, got %s", expectedPath, r.URL.Path) + // Branch exists check returns success + branch := &github.Branch{ + Name: github.Ptr("existing-branch"), + Commit: &github.RepositoryCommit{ + SHA: github.Ptr("abc123"), + }, + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(branch)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + sourceBranch: "main", + branchName: "existing-branch", + wantErr: ErrResourceAlreadyExists, + }, + { + name: "source branch not found", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposBranchesByOwnerByRepoByBranch, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // First call checks if branch exists (should return 404) + if strings.Contains(r.URL.Path, "/new-branch") { + w.WriteHeader(http.StatusNotFound) + return + } + + // Second call gets the source branch (not found) + if strings.Contains(r.URL.Path, "/nonexistent") { + w.WriteHeader(http.StatusNotFound) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusNotFound, + }, + Message: "Branch not found", + })) + } + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + sourceBranch: "nonexistent", + branchName: "new-branch", + wantErr: errors.New("get base branch"), + }, + { + name: "error creating branch ref", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposBranchesByOwnerByRepoByBranch, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // First call checks if branch exists (should return 404) + if strings.Contains(r.URL.Path, "/new-branch") { + w.WriteHeader(http.StatusNotFound) + return + } + + // Second call gets the source branch + if strings.Contains(r.URL.Path, "/main") { + branch := &github.Branch{ + Name: github.Ptr("main"), + Commit: &github.RepositoryCommit{ + SHA: github.Ptr("abc123"), + }, + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(branch)) + } + }), + ), + mockhub.WithRequestMatchHandler( + mockhub.PostReposGitRefsByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusInternalServerError, + }, + Message: "Internal server error", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + sourceBranch: "main", + branchName: "new-branch", + wantErr: errors.New("create branch ref"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a mock client + factory := ProvideFactory() + factory.Client = tt.mockHandler + client := factory.New(context.Background(), "") + + // Call the method being tested + err := client.CreateBranch(context.Background(), tt.owner, tt.repository, tt.sourceBranch, tt.branchName) + + // Check the error + if tt.wantErr != nil { + assert.Error(t, err) + if errors.Is(err, tt.wantErr) { + assert.Equal(t, tt.wantErr, err) + } else { + assert.Contains(t, err.Error(), tt.wantErr.Error()) + } + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestGithubClient_BranchExists(t *testing.T) { + tests := []struct { + name string + mockHandler *http.Client + owner string + repository string + branchName string + want bool + wantErr bool + }{ + { + name: "branch exists", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposBranchesByOwnerByRepoByBranch, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + branch := &github.Branch{ + Name: github.Ptr("existing-branch"), + Commit: &github.RepositoryCommit{ + SHA: github.Ptr("abc123"), + }, + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(branch)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + branchName: "existing-branch", + want: true, + wantErr: false, + }, + { + name: "branch does not exist", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposBranchesByOwnerByRepoByBranch, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusNotFound, + }, + Message: "Branch not found", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + branchName: "non-existent-branch", + want: false, + wantErr: false, + }, + { + name: "error response", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposBranchesByOwnerByRepoByBranch, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusInternalServerError, + }, + Message: "Internal server error", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + branchName: "some-branch", + want: false, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a mock client + factory := ProvideFactory() + factory.Client = tt.mockHandler + client := factory.New(context.Background(), "") + + // Call the method being tested + got, err := client.BranchExists(context.Background(), tt.owner, tt.repository, tt.branchName) + + // Check the error + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + + // Check the result + assert.Equal(t, tt.want, got) + }) + } +} +func TestGithubClient_ListWebhooks(t *testing.T) { + tests := []struct { + name string + mockHandler *http.Client + owner string + repository string + wantWebhooks []WebhookConfig + wantErr error + }{ + { + name: "successful webhooks listing", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposHooksByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + hooks := []*github.Hook{ + { + ID: github.Ptr(int64(1)), + Events: []string{"push", "pull_request"}, + Active: github.Ptr(true), + Config: &github.HookConfig{ + URL: github.Ptr("https://example.com/webhook1"), + ContentType: github.Ptr("json"), + }, + }, + { + ID: github.Ptr(int64(2)), + Events: []string{"issues"}, + Active: github.Ptr(false), + Config: &github.HookConfig{ + URL: github.Ptr("https://example.com/webhook2"), + ContentType: github.Ptr(""), + }, + }, + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(hooks)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + wantWebhooks: []WebhookConfig{ + { + ID: 1, + Events: []string{"push", "pull_request"}, + Active: true, + URL: "https://example.com/webhook1", + ContentType: "json", + }, + { + ID: 2, + Events: []string{"issues"}, + Active: false, + URL: "https://example.com/webhook2", + ContentType: "form", // Default value when empty + }, + }, + wantErr: nil, + }, + { + name: "empty webhooks list", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposHooksByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + hooks := []*github.Hook{} + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(hooks)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + wantWebhooks: []WebhookConfig{}, + wantErr: nil, + }, + { + name: "too many webhooks", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposHooksByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + // Create more webhooks than the maxWebhooks limit + hooks := make([]*github.Hook, maxWebhooks+1) + for i := 0; i < maxWebhooks+1; i++ { + hooks[i] = &github.Hook{ + ID: github.Ptr(int64(i + 1)), + Events: []string{"push"}, + Active: github.Ptr(true), + Config: &github.HookConfig{ + URL: github.Ptr(fmt.Sprintf("https://example.com/webhook%d", i+1)), + ContentType: github.Ptr("json"), + }, + } + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(hooks)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + wantWebhooks: nil, + wantErr: fmt.Errorf("too many webhooks configured (more than %d)", maxWebhooks), + }, + { + name: "service unavailable error", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposHooksByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusServiceUnavailable, + }, + Message: "Service unavailable", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + wantWebhooks: nil, + wantErr: ErrServiceUnavailable, + }, + { + name: "other error", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposHooksByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusInternalServerError, + }, + Message: "Internal server error", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + wantWebhooks: nil, + wantErr: errors.New("Internal server error"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a mock client + factory := ProvideFactory() + factory.Client = tt.mockHandler + client := factory.New(context.Background(), "") + + // Call the method being tested + webhooks, err := client.ListWebhooks(context.Background(), tt.owner, tt.repository) + + // Check the error + if tt.wantErr != nil { + assert.Error(t, err) + if errors.Is(err, tt.wantErr) { + assert.Equal(t, tt.wantErr, err) + } else { + assert.Contains(t, err.Error(), tt.wantErr.Error()) + } + } else { + assert.NoError(t, err) + } + + // Check the result + assert.Equal(t, tt.wantWebhooks, webhooks) + }) + } +} + +func TestGithubClient_CreateWebhook(t *testing.T) { + tests := []struct { + name string + mockHandler *http.Client + owner string + repository string + config WebhookConfig + want WebhookConfig + wantErr error + }{ + { + name: "successful webhook creation", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.PostReposHooksByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Verify the request body contains the correct webhook config + body, err := io.ReadAll(r.Body) + require.NoError(t, err) + + hook := &github.Hook{} + require.NoError(t, json.Unmarshal(body, hook)) + + assert.Equal(t, "https://example.com/webhook", hook.Config.GetURL()) + assert.Equal(t, "json", hook.Config.GetContentType()) + assert.Equal(t, "secret123", hook.Config.GetSecret()) + assert.Equal(t, []string{"push", "pull_request"}, hook.Events) + assert.True(t, hook.GetActive()) + + // Return a created hook + createdHook := &github.Hook{ + ID: github.Ptr(int64(123)), + Events: []string{"push", "pull_request"}, + Active: github.Ptr(true), + Config: &github.HookConfig{ + URL: github.Ptr("https://example.com/webhook"), + ContentType: github.Ptr("json"), + // Secret is not returned by GitHub API + }, + } + + w.WriteHeader(http.StatusCreated) + require.NoError(t, json.NewEncoder(w).Encode(createdHook)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + config: WebhookConfig{ + Events: []string{"push", "pull_request"}, + Active: true, + URL: "https://example.com/webhook", + ContentType: "json", + Secret: "secret123", + }, + want: WebhookConfig{ + ID: 123, + Events: []string{"push", "pull_request"}, + Active: true, + URL: "https://example.com/webhook", + ContentType: "json", + Secret: "secret123", + }, + wantErr: nil, + }, + { + name: "default content type to form", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.PostReposHooksByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, err := io.ReadAll(r.Body) + require.NoError(t, err) + + hook := &github.Hook{} + require.NoError(t, json.Unmarshal(body, hook)) + + // Verify content type was defaulted to "form" + assert.Equal(t, "form", hook.Config.GetContentType()) + + createdHook := &github.Hook{ + ID: github.Ptr(int64(123)), + Events: []string{"push"}, + Active: github.Ptr(true), + Config: &github.HookConfig{ + URL: github.Ptr("https://example.com/webhook"), + ContentType: github.Ptr("form"), + }, + } + + w.WriteHeader(http.StatusCreated) + require.NoError(t, json.NewEncoder(w).Encode(createdHook)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + config: WebhookConfig{ + Events: []string{"push"}, + Active: true, + URL: "https://example.com/webhook", + Secret: "secret123", + // ContentType intentionally omitted + }, + want: WebhookConfig{ + ID: 123, + Events: []string{"push"}, + Active: true, + URL: "https://example.com/webhook", + ContentType: "form", + Secret: "secret123", + }, + wantErr: nil, + }, + { + name: "service unavailable error", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.PostReposHooksByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusServiceUnavailable, + }, + Message: "Service unavailable", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + config: WebhookConfig{ + Events: []string{"push"}, + Active: true, + URL: "https://example.com/webhook", + ContentType: "json", + Secret: "secret123", + }, + want: WebhookConfig{}, + wantErr: ErrServiceUnavailable, + }, + { + name: "other error", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.PostReposHooksByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusInternalServerError, + }, + Message: "Internal server error", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + config: WebhookConfig{ + Events: []string{"push"}, + Active: true, + URL: "https://example.com/webhook", + ContentType: "json", + Secret: "secret123", + }, + want: WebhookConfig{}, + wantErr: errors.New("Internal server error"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a mock client + factory := ProvideFactory() + factory.Client = tt.mockHandler + client := factory.New(context.Background(), "") + + // Call the method being tested + got, err := client.CreateWebhook(context.Background(), tt.owner, tt.repository, tt.config) + + // Check the error + if tt.wantErr != nil { + assert.Error(t, err) + if errors.Is(err, tt.wantErr) { + assert.Equal(t, tt.wantErr, err) + } else { + assert.Contains(t, err.Error(), tt.wantErr.Error()) + } + } else { + assert.NoError(t, err) + } + + // Check the result + assert.Equal(t, tt.want, got) + }) + } +} + +func TestGithubClient_GetWebhook(t *testing.T) { + tests := []struct { + name string + mockHandler *http.Client + owner string + repository string + webhookID int64 + want WebhookConfig + wantErr error + }{ + { + name: "successful webhook retrieval", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposHooksByOwnerByRepoByHookId, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + hook := &github.Hook{ + ID: github.Ptr(int64(123)), + Events: []string{"push", "pull_request"}, + Active: github.Ptr(true), + Config: &github.HookConfig{ + URL: github.Ptr("https://example.com/webhook"), + ContentType: github.Ptr("json"), + // Secret is not returned by GitHub API + }, + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(hook)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + webhookID: 123, + want: WebhookConfig{ + ID: 123, + Events: []string{"push", "pull_request"}, + Active: true, + URL: "https://example.com/webhook", + ContentType: "json", + }, + wantErr: nil, + }, + + { + name: "empty content type defaults to json", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposHooksByOwnerByRepoByHookId, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + hook := &github.Hook{ + ID: github.Ptr(int64(456)), + Events: []string{"push"}, + Active: github.Ptr(true), + Config: &github.HookConfig{ + URL: github.Ptr("https://example.com/webhook-empty-content"), + ContentType: github.Ptr(""), // Empty content type + }, + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(hook)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + webhookID: 456, + want: WebhookConfig{ + ID: 456, + Events: []string{"push"}, + Active: true, + URL: "https://example.com/webhook-empty-content", + ContentType: "json", // Should default to "json" + }, + wantErr: nil, + }, + { + name: "webhook not found", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposHooksByOwnerByRepoByHookId, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusNotFound, + }, + Message: "Not Found", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + webhookID: 999, + want: WebhookConfig{}, + wantErr: ErrResourceNotFound, + }, + { + name: "service unavailable", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposHooksByOwnerByRepoByHookId, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusServiceUnavailable, + }, + Message: "Service Unavailable", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + webhookID: 123, + want: WebhookConfig{}, + wantErr: ErrServiceUnavailable, + }, + { + name: "other error", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposHooksByOwnerByRepoByHookId, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusInternalServerError, + }, + Message: "Internal server error", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + webhookID: 123, + want: WebhookConfig{}, + wantErr: errors.New("Internal server error"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a mock client + factory := ProvideFactory() + factory.Client = tt.mockHandler + client := factory.New(context.Background(), "") + + // Call the method being tested + got, err := client.GetWebhook(context.Background(), tt.owner, tt.repository, tt.webhookID) + + // Check the error + if tt.wantErr != nil { + assert.Error(t, err) + if errors.Is(err, tt.wantErr) { + assert.Equal(t, tt.wantErr, err) + } else { + assert.Contains(t, err.Error(), tt.wantErr.Error()) + } + } else { + assert.NoError(t, err) + } + + // Check the result + assert.Equal(t, tt.want, got) + }) + } +} + +func TestGithubClient_DeleteWebhook(t *testing.T) { + tests := []struct { + name string + mockHandler *http.Client + owner string + repository string + webhookID int64 + wantErr error + }{ + { + name: "successful webhook deletion", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.DeleteReposHooksByOwnerByRepoByHookId, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNoContent) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + webhookID: 123, + wantErr: nil, + }, + { + name: "webhook not found", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.DeleteReposHooksByOwnerByRepoByHookId, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusNotFound, + }, + Message: "Not found", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + webhookID: 456, + wantErr: ErrResourceNotFound, + }, + { + name: "service unavailable", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.DeleteReposHooksByOwnerByRepoByHookId, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusServiceUnavailable, + }, + Message: "Service unavailable", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + webhookID: 789, + wantErr: ErrServiceUnavailable, + }, + { + name: "other error", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.DeleteReposHooksByOwnerByRepoByHookId, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusInternalServerError, + }, + Message: "Internal server error", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + webhookID: 101, + wantErr: errors.New("Internal server error"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a mock client + factory := ProvideFactory() + factory.Client = tt.mockHandler + client := factory.New(context.Background(), "") + + // Call the method being tested + err := client.DeleteWebhook(context.Background(), tt.owner, tt.repository, tt.webhookID) + + // Check the error + if tt.wantErr != nil { + assert.Error(t, err) + if errors.Is(err, tt.wantErr) { + assert.Equal(t, tt.wantErr, err) + } else { + assert.Contains(t, err.Error(), tt.wantErr.Error()) + } + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestGithubClient_EditWebhook(t *testing.T) { + tests := []struct { + name string + mockHandler *http.Client + owner string + repository string + config WebhookConfig + wantErr error + }{ + { + name: "successful webhook edit", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.PatchReposHooksByOwnerByRepoByHookId, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Verify the request body contains the correct webhook config + body, err := io.ReadAll(r.Body) + require.NoError(t, err) + + hook := &github.Hook{} + require.NoError(t, json.Unmarshal(body, hook)) + + assert.Equal(t, "https://example.com/webhook-updated", hook.Config.GetURL()) + assert.Equal(t, "json", hook.Config.GetContentType()) + assert.Equal(t, "updated-secret", hook.Config.GetSecret()) + assert.Equal(t, []string{"push", "pull_request", "issues"}, hook.Events) + assert.True(t, hook.GetActive()) + + // Return the updated hook + updatedHook := &github.Hook{ + ID: github.Ptr(int64(123)), + Events: []string{"push", "pull_request", "issues"}, + Active: github.Ptr(true), + Config: &github.HookConfig{ + URL: github.Ptr("https://example.com/webhook-updated"), + ContentType: github.Ptr("json"), + // Secret is not returned by GitHub API + }, + } + + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(updatedHook)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + config: WebhookConfig{ + ID: 123, + Events: []string{"push", "pull_request", "issues"}, + Active: true, + URL: "https://example.com/webhook-updated", + ContentType: "json", + Secret: "updated-secret", + }, + wantErr: nil, + }, + { + name: "default content type to form", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.PatchReposHooksByOwnerByRepoByHookId, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Verify the request body contains the correct webhook config + body, err := io.ReadAll(r.Body) + require.NoError(t, err) + + hook := &github.Hook{} + require.NoError(t, json.Unmarshal(body, hook)) + + // Verify content type was defaulted to "form" + assert.Equal(t, "form", hook.Config.GetContentType()) + assert.Equal(t, "https://example.com/webhook", hook.Config.GetURL()) + assert.Equal(t, "secret123", hook.Config.GetSecret()) + assert.Equal(t, []string{"push"}, hook.Events) + assert.True(t, hook.GetActive()) + + // Return the updated hook + updatedHook := &github.Hook{ + ID: github.Ptr(int64(123)), + Events: []string{"push"}, + Active: github.Ptr(true), + Config: &github.HookConfig{ + URL: github.Ptr("https://example.com/webhook"), + ContentType: github.Ptr("form"), + // Secret is not returned by GitHub API + }, + } + + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(updatedHook)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + config: WebhookConfig{ + ID: 123, + Events: []string{"push"}, + Active: true, + URL: "https://example.com/webhook", + ContentType: "", // Empty content type should default to "form" + Secret: "secret123", + }, + wantErr: nil, + }, + { + name: "service unavailable error", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.PatchReposHooksByOwnerByRepoByHookId, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusServiceUnavailable, + }, + Message: "Service unavailable", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + config: WebhookConfig{ + ID: 123, + Events: []string{"push"}, + Active: true, + URL: "https://example.com/webhook", + ContentType: "json", + Secret: "secret123", + }, + wantErr: ErrServiceUnavailable, + }, + { + name: "other error", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.PatchReposHooksByOwnerByRepoByHookId, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusInternalServerError, + }, + Message: "Internal server error", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + config: WebhookConfig{ + ID: 123, + Events: []string{"push"}, + Active: true, + URL: "https://example.com/webhook", + ContentType: "json", + Secret: "secret123", + }, + wantErr: errors.New("Internal server error"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a mock client + factory := ProvideFactory() + factory.Client = tt.mockHandler + client := factory.New(context.Background(), "") + + // Call the method being tested + err := client.EditWebhook(context.Background(), tt.owner, tt.repository, tt.config) + + // Check the error + if tt.wantErr != nil { + assert.Error(t, err) + if errors.Is(err, tt.wantErr) { + assert.Equal(t, tt.wantErr, err) + } else { + assert.Contains(t, err.Error(), tt.wantErr.Error()) + } + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestGithubClient_ListPullRequestFiles(t *testing.T) { + tests := []struct { + name string + mockHandler *http.Client + owner string + repository string + number int + wantFiles []CommitFile + wantErr error + }{ + { + name: "successful pull request files listing", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposPullsFilesByOwnerByRepoByPullNumber, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + files := []*github.CommitFile{ + { + Filename: github.Ptr("file1.txt"), + Additions: github.Ptr(10), + Deletions: github.Ptr(5), + Changes: github.Ptr(15), + Status: github.Ptr("modified"), + Patch: github.Ptr("@@ -1,5 +1,10 @@"), + }, + { + Filename: github.Ptr("file2.txt"), + Additions: github.Ptr(20), + Deletions: github.Ptr(0), + Changes: github.Ptr(20), + Status: github.Ptr("added"), + Patch: github.Ptr("@@ -0,0 +1,20 @@"), + }, + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(files)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + number: 123, + wantFiles: []CommitFile{ + &github.CommitFile{ + Filename: github.Ptr("file1.txt"), + Additions: github.Ptr(10), + Deletions: github.Ptr(5), + Changes: github.Ptr(15), + Status: github.Ptr("modified"), + Patch: github.Ptr("@@ -1,5 +1,10 @@"), + }, + &github.CommitFile{ + Filename: github.Ptr("file2.txt"), + Additions: github.Ptr(20), + Deletions: github.Ptr(0), + Changes: github.Ptr(20), + Status: github.Ptr("added"), + Patch: github.Ptr("@@ -0,0 +1,20 @@"), + }, + }, + wantErr: nil, + }, + { + name: "empty files list", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposPullsFilesByOwnerByRepoByPullNumber, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + files := []*github.CommitFile{} + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(files)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + number: 456, + wantFiles: []CommitFile{}, + wantErr: nil, + }, + { + name: "too many files", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposPullsFilesByOwnerByRepoByPullNumber, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + // Create more files than the maxPRFiles limit + files := make([]*github.CommitFile, maxPRFiles+1) + for i := 0; i < maxPRFiles+1; i++ { + files[i] = &github.CommitFile{ + Filename: github.Ptr(fmt.Sprintf("file%d.txt", i+1)), + Additions: github.Ptr(i + 1), + Deletions: github.Ptr(0), + Changes: github.Ptr(i + 1), + Status: github.Ptr("added"), + } + } + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(files)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + number: 789, + wantFiles: nil, + wantErr: fmt.Errorf("pull request contains too many files (more than %d)", maxPRFiles), + }, + { + name: "service unavailable error", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposPullsFilesByOwnerByRepoByPullNumber, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusServiceUnavailable, + }, + Message: "Service unavailable", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + number: 101, + wantFiles: nil, + wantErr: ErrServiceUnavailable, + }, + { + name: "other error", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.GetReposPullsFilesByOwnerByRepoByPullNumber, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusInternalServerError, + }, + Message: "Internal server error", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + number: 202, + wantFiles: nil, + wantErr: errors.New("Internal server error"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a mock client + factory := ProvideFactory() + factory.Client = tt.mockHandler + client := factory.New(context.Background(), "") + + // Call the method being tested + files, err := client.ListPullRequestFiles(context.Background(), tt.owner, tt.repository, tt.number) + + // Check the error + if tt.wantErr != nil { + assert.Error(t, err) + if errors.Is(err, tt.wantErr) { + assert.Equal(t, tt.wantErr, err) + } else { + assert.Contains(t, err.Error(), tt.wantErr.Error()) + } + } else { + assert.NoError(t, err) + } + + // Check the result + assert.Equal(t, tt.wantFiles, files) + }) + } +} + +func TestCreatePullRequestComment(t *testing.T) { + tests := []struct { + name string + mockHandler *http.Client + owner string + repository string + number int + body string + wantErr error + }{ + { + name: "successful comment creation", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.PostReposIssuesCommentsByOwnerByRepoByIssueNumber, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Verify the request body contains the correct comment + body, err := io.ReadAll(r.Body) + require.NoError(t, err) + + comment := &github.IssueComment{} + require.NoError(t, json.Unmarshal(body, comment)) + assert.Equal(t, "Test comment", comment.GetBody()) + + // Return the created comment + createdComment := &github.IssueComment{ + ID: github.Ptr(int64(123)), + Body: github.Ptr("Test comment"), + } + + w.WriteHeader(http.StatusCreated) + require.NoError(t, json.NewEncoder(w).Encode(createdComment)) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + number: 101, + body: "Test comment", + wantErr: nil, + }, + { + name: "service unavailable error", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.PostReposIssuesCommentsByOwnerByRepoByIssueNumber, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusServiceUnavailable, + }, + Message: "Service unavailable", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + number: 101, + body: "Test comment", + wantErr: ErrServiceUnavailable, + }, + { + name: "other error", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.PostReposIssuesCommentsByOwnerByRepoByIssueNumber, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusInternalServerError, + }, + Message: "Internal server error", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + number: 101, + body: "Test comment", + wantErr: errors.New("Internal server error"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a mock client + factory := ProvideFactory() + factory.Client = tt.mockHandler + client := factory.New(context.Background(), "") + + // Call the method being tested + err := client.CreatePullRequestComment(context.Background(), tt.owner, tt.repository, tt.number, tt.body) + + // Check the error + if tt.wantErr != nil { + assert.Error(t, err) + if errors.Is(err, tt.wantErr) { + assert.Equal(t, tt.wantErr, err) + } else { + assert.Contains(t, err.Error(), tt.wantErr.Error()) + } + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestPaginatedList(t *testing.T) { + tests := []struct { + name string + mockSetup func() (func(context.Context, *github.ListOptions) ([]string, *github.Response, error), listOptions) + want []string + wantErr error + }{ + { + name: "single page", + mockSetup: func() (func(context.Context, *github.ListOptions) ([]string, *github.Response, error), listOptions) { + items := []string{"item1", "item2", "item3"} + listFn := func(_ context.Context, _ *github.ListOptions) ([]string, *github.Response, error) { + return items, &github.Response{ + NextPage: 0, + }, nil + } + return listFn, defaultListOptions(100) + }, + want: []string{"item1", "item2", "item3"}, + wantErr: nil, + }, + { + name: "multiple pages", + mockSetup: func() (func(context.Context, *github.ListOptions) ([]string, *github.Response, error), listOptions) { + page1 := []string{"item1", "item2"} + page2 := []string{"item3", "item4"} + page3 := []string{"item5"} + + var callCount int + listFn := func(_ context.Context, opts *github.ListOptions) ([]string, *github.Response, error) { + callCount++ + switch callCount { + case 1: + return page1, &github.Response{ + NextPage: 2, + }, nil + case 2: + assert.Equal(t, 2, opts.Page) + return page2, &github.Response{ + NextPage: 3, + }, nil + case 3: + assert.Equal(t, 3, opts.Page) + return page3, &github.Response{ + NextPage: 0, + }, nil + default: + return nil, nil, errors.New("unexpected call") + } + } + return listFn, defaultListOptions(100) + }, + want: []string{"item1", "item2", "item3", "item4", "item5"}, + wantErr: nil, + }, + { + name: "error on first page", + mockSetup: func() (func(context.Context, *github.ListOptions) ([]string, *github.Response, error), listOptions) { + listFn := func(_ context.Context, _ *github.ListOptions) ([]string, *github.Response, error) { + return nil, &github.Response{}, errors.New("API error") + } + return listFn, defaultListOptions(100) + }, + want: nil, + wantErr: errors.New("API error"), + }, + { + name: "service unavailable error", + mockSetup: func() (func(context.Context, *github.ListOptions) ([]string, *github.Response, error), listOptions) { + listFn := func(_ context.Context, _ *github.ListOptions) ([]string, *github.Response, error) { + return nil, &github.Response{}, &github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusServiceUnavailable, + }, + } + } + return listFn, defaultListOptions(100) + }, + want: nil, + wantErr: ErrServiceUnavailable, + }, + { + name: "resource not found error", + mockSetup: func() (func(context.Context, *github.ListOptions) ([]string, *github.Response, error), listOptions) { + listFn := func(_ context.Context, _ *github.ListOptions) ([]string, *github.Response, error) { + return nil, &github.Response{}, &github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusNotFound, + }, + } + } + return listFn, defaultListOptions(100) + }, + want: nil, + wantErr: ErrResourceNotFound, + }, + { + name: "too many items error", + mockSetup: func() (func(context.Context, *github.ListOptions) ([]string, *github.Response, error), listOptions) { + listFn := func(_ context.Context, _ *github.ListOptions) ([]string, *github.Response, error) { + // Return more items than the max allowed + items := make([]string, 10) + for i := range items { + items[i] = fmt.Sprintf("item%d", i+1) + } + return items, &github.Response{ + NextPage: 2, + }, nil + } + return listFn, listOptions{ + ListOptions: github.ListOptions{ + Page: 1, + PerPage: 100, + }, + MaxItems: 5, // Set max items to less than what we'll return + } + }, + want: nil, + wantErr: ErrTooManyItems, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + listFn, opts := tt.mockSetup() + + got, err := paginatedList(context.Background(), listFn, opts) + + if tt.wantErr != nil { + assert.Error(t, err) + if errors.Is(err, tt.wantErr) { + assert.Equal(t, tt.wantErr, err) + } else { + assert.Contains(t, err.Error(), tt.wantErr.Error()) + } + assert.Nil(t, got) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + } + }) + } +} + +func TestDefaultListOptions(t *testing.T) { + tests := []struct { + name string + maxItems int + want listOptions + }{ + { + name: "with zero max items", + maxItems: 0, + want: listOptions{ + ListOptions: github.ListOptions{ + Page: 1, + PerPage: 100, + }, + MaxItems: 0, + }, + }, + { + name: "with positive max items", + maxItems: 50, + want: listOptions{ + ListOptions: github.ListOptions{ + Page: 1, + PerPage: 100, + }, + MaxItems: 50, + }, + }, + { + name: "with large max items", + maxItems: 1000, + want: listOptions{ + ListOptions: github.ListOptions{ + Page: 1, + PerPage: 100, + }, + MaxItems: 1000, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := defaultListOptions(tt.maxItems) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestRealRepositoryContent(t *testing.T) { + t.Run("IsDirectory", func(t *testing.T) { + tests := []struct { + name string + repoType string + want bool + }{ + { + name: "directory type", + repoType: "dir", + want: true, + }, + { + name: "file type", + repoType: "file", + want: false, + }, + { + name: "empty type", + repoType: "", + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + repoType := tt.repoType + content := realRepositoryContent{ + real: &github.RepositoryContent{ + Type: &repoType, + }, + } + got := content.IsDirectory() + assert.Equal(t, tt.want, got) + }) + } + }) + + t.Run("GetFileContent", func(t *testing.T) { + fileContent := "test content" + content := realRepositoryContent{ + real: &github.RepositoryContent{ + Content: &fileContent, + }, + } + got, err := content.GetFileContent() + assert.NoError(t, err) + assert.Equal(t, fileContent, got) + }) + + t.Run("IsSymlink", func(t *testing.T) { + tests := []struct { + name string + target *string + want bool + }{ + { + name: "is symlink", + target: github.Ptr("target"), + want: true, + }, + { + name: "not symlink", + target: nil, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + content := realRepositoryContent{ + real: &github.RepositoryContent{ + Target: tt.target, + }, + } + got := content.IsSymlink() + assert.Equal(t, tt.want, got) + }) + } + }) + + t.Run("GetPath", func(t *testing.T) { + path := "path/to/file" + content := realRepositoryContent{ + real: &github.RepositoryContent{ + Path: &path, + }, + } + got := content.GetPath() + assert.Equal(t, path, got) + }) + + t.Run("GetSHA", func(t *testing.T) { + sha := "abc123" + content := realRepositoryContent{ + real: &github.RepositoryContent{ + SHA: &sha, + }, + } + got := content.GetSHA() + assert.Equal(t, sha, got) + }) + + t.Run("GetSize", func(t *testing.T) { + t.Run("with size field", func(t *testing.T) { + size := 42 + content := realRepositoryContent{ + real: &github.RepositoryContent{ + Size: &size, + }, + } + got := content.GetSize() + assert.Equal(t, int64(size), got) + }) + + t.Run("with content field", func(t *testing.T) { + fileContent := "test content" + content := realRepositoryContent{ + real: &github.RepositoryContent{ + Content: &fileContent, + }, + } + got := content.GetSize() + assert.Equal(t, int64(len(fileContent)), got) + }) + + t.Run("with no size or content", func(t *testing.T) { + content := realRepositoryContent{ + real: &github.RepositoryContent{}, + } + got := content.GetSize() + assert.Equal(t, int64(0), got) + }) + }) +} diff --git a/pkg/registry/apis/provisioning/repository/github/mock_client.go b/pkg/registry/apis/provisioning/repository/github/mock_client.go index a564d1c4d52..6ea39c8a67f 100644 --- a/pkg/registry/apis/provisioning/repository/github/mock_client.go +++ b/pkg/registry/apis/provisioning/repository/github/mock_client.go @@ -80,55 +80,6 @@ func (_c *MockClient_BranchExists_Call) RunAndReturn(run func(context.Context, s return _c } -// ClearAllPullRequestFileComments provides a mock function with given fields: ctx, owner, repository, number -func (_m *MockClient) ClearAllPullRequestFileComments(ctx context.Context, owner string, repository string, number int) error { - ret := _m.Called(ctx, owner, repository, number) - - if len(ret) == 0 { - panic("no return value specified for ClearAllPullRequestFileComments") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, int) error); ok { - r0 = rf(ctx, owner, repository, number) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockClient_ClearAllPullRequestFileComments_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClearAllPullRequestFileComments' -type MockClient_ClearAllPullRequestFileComments_Call struct { - *mock.Call -} - -// ClearAllPullRequestFileComments is a helper method to define mock.On call -// - ctx context.Context -// - owner string -// - repository string -// - number int -func (_e *MockClient_Expecter) ClearAllPullRequestFileComments(ctx interface{}, owner interface{}, repository interface{}, number interface{}) *MockClient_ClearAllPullRequestFileComments_Call { - return &MockClient_ClearAllPullRequestFileComments_Call{Call: _e.mock.On("ClearAllPullRequestFileComments", ctx, owner, repository, number)} -} - -func (_c *MockClient_ClearAllPullRequestFileComments_Call) Run(run func(ctx context.Context, owner string, repository string, number int)) *MockClient_ClearAllPullRequestFileComments_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(int)) - }) - return _c -} - -func (_c *MockClient_ClearAllPullRequestFileComments_Call) Return(_a0 error) *MockClient_ClearAllPullRequestFileComments_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockClient_ClearAllPullRequestFileComments_Call) RunAndReturn(run func(context.Context, string, string, int) error) *MockClient_ClearAllPullRequestFileComments_Call { - _c.Call.Return(run) - return _c -} - // Commits provides a mock function with given fields: ctx, owner, repository, path, branch func (_m *MockClient) Commits(ctx context.Context, owner string, repository string, path string, branch string) ([]Commit, error) { ret := _m.Called(ctx, owner, repository, path, branch) @@ -405,56 +356,6 @@ func (_c *MockClient_CreatePullRequestComment_Call) RunAndReturn(run func(contex return _c } -// CreatePullRequestFileComment provides a mock function with given fields: ctx, owner, repository, number, comment -func (_m *MockClient) CreatePullRequestFileComment(ctx context.Context, owner string, repository string, number int, comment FileComment) error { - ret := _m.Called(ctx, owner, repository, number, comment) - - if len(ret) == 0 { - panic("no return value specified for CreatePullRequestFileComment") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, int, FileComment) error); ok { - r0 = rf(ctx, owner, repository, number, comment) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockClient_CreatePullRequestFileComment_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreatePullRequestFileComment' -type MockClient_CreatePullRequestFileComment_Call struct { - *mock.Call -} - -// CreatePullRequestFileComment is a helper method to define mock.On call -// - ctx context.Context -// - owner string -// - repository string -// - number int -// - comment FileComment -func (_e *MockClient_Expecter) CreatePullRequestFileComment(ctx interface{}, owner interface{}, repository interface{}, number interface{}, comment interface{}) *MockClient_CreatePullRequestFileComment_Call { - return &MockClient_CreatePullRequestFileComment_Call{Call: _e.mock.On("CreatePullRequestFileComment", ctx, owner, repository, number, comment)} -} - -func (_c *MockClient_CreatePullRequestFileComment_Call) Run(run func(ctx context.Context, owner string, repository string, number int, comment FileComment)) *MockClient_CreatePullRequestFileComment_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(int), args[4].(FileComment)) - }) - return _c -} - -func (_c *MockClient_CreatePullRequestFileComment_Call) Return(_a0 error) *MockClient_CreatePullRequestFileComment_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockClient_CreatePullRequestFileComment_Call) RunAndReturn(run func(context.Context, string, string, int, FileComment) error) *MockClient_CreatePullRequestFileComment_Call { - _c.Call.Return(run) - return _c -} - // CreateWebhook provides a mock function with given fields: ctx, owner, repository, cfg func (_m *MockClient) CreateWebhook(ctx context.Context, owner string, repository string, cfg WebhookConfig) (WebhookConfig, error) { ret := _m.Called(ctx, owner, repository, cfg) diff --git a/pkg/registry/apis/provisioning/repository/local_test.go b/pkg/registry/apis/provisioning/repository/local_test.go index c924a03d910..ce52af59a8e 100644 --- a/pkg/registry/apis/provisioning/repository/local_test.go +++ b/pkg/registry/apis/provisioning/repository/local_test.go @@ -56,6 +56,7 @@ func TestLocalResolver(t *testing.T) { "client.go", "factory.go", "impl.go", + "impl_test.go", "mock_client.go", "mock_commit_file.go", "mock_repository_content.go", From 9c125b812dfb0acab9a044701543034685ae0a66 Mon Sep 17 00:00:00 2001 From: Jacob Valdez Date: Tue, 22 Apr 2025 12:21:15 -0500 Subject: [PATCH 006/146] Docs: Making some slight adjustments to migration assistant (#104180) --- .../administration/migration-guide/_index.md | 10 ++--- .../cloud-migration-assistant.md | 38 +++++++++++++------ 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/docs/sources/administration/migration-guide/_index.md b/docs/sources/administration/migration-guide/_index.md index deb7de8c533..94576db65c0 100644 --- a/docs/sources/administration/migration-guide/_index.md +++ b/docs/sources/administration/migration-guide/_index.md @@ -15,9 +15,9 @@ title: Migrate from Grafana OSS/Enterprise to Grafana Cloud When you decide to migrate from your self-managed Grafana instance to Grafana Cloud, you can benefit from the convenience of a managed observability platform, additional cloud-only features, and robust security. There are a couple of key approaches to help you transition to Grafana Cloud. -| Migration type | Tools used | Availability | Migratable resources | -| :------------- | :-------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Manual |
  • Command line utilities
  • The Grafana HTTP API
| Generally available in all versions of Grafana OSS/Enterprise | The entire Grafana instance | -| Automated | The Grafana Cloud Migration Assistant | Available in public preview from Grafana v11.2 using the `onPremToCloudMigrations` feature toggle. This toggle is enabled by default in Grafana v11.5 and later. |
  • Dashboards
  • Folders
  • Data sources
  • App Plugins
  • Panel Plugins
  • Library Panels
  • Grafana Alerting resources
| +| Migration type | Tools used | Availability | Migratable resources | +| :------------- | :-------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Manual |
  • Command line utilities
  • The Grafana HTTP API
| Generally available in all versions of Grafana OSS/Enterprise | The entire Grafana instance | +| Automated | The Grafana Cloud Migration Assistant | Generally available in Grafana v12 and available in public preview from Grafana v11.2 to v11.6 using the `onPremToCloudMigrations` feature toggle. This toggle is enabled by default in Grafana v11.5 and later. |
  • Dashboards
  • Folders
  • Data sources
  • App Plugins
  • Panel Plugins
  • Library Panels
  • Grafana Alerting resources
| -Our detailed [migration guide](https://www.grafana.com/docs/grafana-cloud/account-management/migration-guide/manually-migrate-to-grafana-cloud/) explains the key steps and scripts to manually migrate your resources to Grafana Cloud, covering a comprehensive set of resources in your Grafana instance. Alternatively, the [Grafana Cloud Migration Assistant](https://www.grafana.com/docs/grafana-cloud/account-management/migration-guide/cloud-migration-assistant/), available in public preview in Grafana v11.2 and later, automates the migration process across a broad range of Grafana resources. You can use the migration assistant to migrate a large proportion of your Grafana resources and then, if needed, leverage the migration guide to migrate the rest. +Our detailed [migration guide](https://grafana.com/docs/grafana-cloud/account-management/migration-guide/manually-migrate-to-grafana-cloud/) explains the key steps and scripts to manually migrate your resources to Grafana Cloud, covering a comprehensive set of resources in your Grafana instance. Alternatively, the [Grafana Cloud Migration Assistant](https://grafana.com/docs/grafana-cloud/account-management/migration-guide/cloud-migration-assistant/), available in public preview in Grafana v11.2 and later, automates the migration process across a broad range of Grafana resources. You can use the migration assistant to migrate a large proportion of your Grafana resources and then, if needed, leverage the migration guide to migrate the rest. diff --git a/docs/sources/administration/migration-guide/cloud-migration-assistant.md b/docs/sources/administration/migration-guide/cloud-migration-assistant.md index 279747ffc0a..8f009fad5d0 100644 --- a/docs/sources/administration/migration-guide/cloud-migration-assistant.md +++ b/docs/sources/administration/migration-guide/cloud-migration-assistant.md @@ -44,10 +44,10 @@ The following resources are supported by the migration assistant: To use the Grafana migration assistant, you need: -- Grafana v11.2 or above with the `onPremToCloudMigrations` feature toggle enabled. In Grafana 11.5, this is enabled by default. For more information on how to enable a feature toggle, refer to [Configure feature toggles](https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/feature-toggles/#configure-feature-toggles). +- Grafana v11.2 or above with the `onPremToCloudMigrations` feature toggle enabled. In Grafana 11.5, this is enabled by default. For more information on how to enable a feature toggle, refer to [Configure feature toggles](https://grafana.com/docs/grafana//setup-grafana/configure-grafana/feature-toggles/#configure-feature-toggles). - A [Grafana Cloud Stack](https://grafana.com/docs/grafana-cloud/get-started/) you intend to migrate your resources to. - [`Admin`](https://grafana.com/docs/grafana-cloud/account-management/authentication-and-permissions/cloud-roles/) access to the Grafana Cloud Stack. To check your access level, go to `https://grafana.com/orgs//members`. -- [Grafana server administrator](https://grafana.com/docs/grafana/latest/administration/roles-and-permissions/#grafana-server-administrators) access to your existing Grafana OSS/Enterprise instance. To check your access level, go to `https:///admin/users`. +- [Grafana server administrator](https://grafana.com/docs/grafana//administration/roles-and-permissions/#grafana-server-administrators) access to your existing Grafana OSS/Enterprise instance. To check your access level, go to `https:///admin/users`. - Internet access from your existing Grafana OSS/Enterprise instance. ## Access the migration assistant @@ -59,7 +59,7 @@ In Grafana Enterprise, the server administrator has access to the migration assi ### Grant access in Grafana Enterprise {{< admonition type="important">}} -You must [configure RBAC](https://grafana.com/docs/grafana/latest/administration/roles-and-permissions/access-control/configure-rbac/) before you can grant other administrators access to the Grafana Migration Assistant. +You must [configure RBAC](https://grafana.com/docs/grafana//administration/roles-and-permissions/access-control/configure-rbac/) before you can grant other administrators access to the Grafana Migration Assistant. {{< /admonition >}} To grant other Admins access to the migration assistant in Grafana Enterprise: @@ -96,7 +96,8 @@ You can use the migration assistant to generate a migration token on your Grafan After connecting to the cloud stack, this is the empty state of the migration assistant. You need to create a snapshot of the self-managed Grafana instance to upload it to the cloud stack. -1. Select the checkbox next to each resource you want to migrate to your cloud stack. +1. From Grafana v12.0, select the checkbox next to each resource you want to migrate to your cloud stack. + {{< admonition type="note" >}} Some resources can't be uploaded to your cloud stack alone because they rely on other resources: | Desired resource | Requires | @@ -113,6 +114,9 @@ After connecting to the cloud stack, this is the empty state of the migration as | Contact Points | Notification Templates | | Mute Timings | Nothing else | {{< /admonition >}} + + In Grafana v11.2 to v11.6, you can't select specific resources to include in the snapshot, such as only dashboards. All supported resources are included by default. + 1. Click **Build snapshot** ![A list of resources selected for migration and the Build snapshot button](/media/docs/grafana/screenshot-grafana-12-select-resources.png) @@ -125,7 +129,7 @@ After a snapshot is created, a list of resources appears with resource Type and 1. Use the assistant's real-time progress tracking to monitor the migration. The status changes to 'Uploaded to cloud' for resources successfully copied to the cloud. - You can group and sort resources during and after the migration: + From Grafana v12.0, you can group and sort resources during and after the migration: - Click **Name** to sort resources alphabetically. - Click **Type** to group and sort by resource type. @@ -139,11 +143,15 @@ After a snapshot is created, a list of resources appears with resource Type and ## Snapshots created by the migration assistant -The migration assistant currently supports a subset of all resources available in Grafana. Refer to [Supported Resources](https://wwww.grafana.com/docs/grafana-cloud/account-management/cloud-migration-assistant/#supported-resources) for more details. +The migration assistant currently supports a subset of all resources available in Grafana. Refer to [Supported Resources](https://grafana.com/docs/grafana-cloud/account-management/cloud-migration-assistant/#supported-resources) for more details. -When you create a snapshot, the migration assistant makes a copy of all supported resources and saves them in the snapshot. The snapshot reflects the current state of the resources when the snapshot is built and is stored locally on your instance, ready to be uploaded in the last stage. It is currently not possible to select specific resources to include in the snapshot, such as only dashboards. All supported resources are included by default. +When you create a snapshot, the migration assistant makes a copy of all the resources you select and saves them in the snapshot. The snapshot reflects the current state of the resources when the snapshot is built and is stored locally on your instance, ready to be uploaded in the last stage. -Resources saved in the snapshot are strictly limited to the resources stored within an organization. This is important to note if there are multiple organizations used in your Grafana instance. If you want to migrate multiple organizations, refer to [Migrate multiple organizations](https://wwww.grafana.com/docs/grafana-cloud/account-management/cloud-migration-assistant/#migrate-multiple-organizations) for more information and guidance. +{{< admonition type="note" >}} +In Grafana v11.2 to v11.6, you can't select specific resources to include in the snapshot, such as only dashboards. All supported resources are included by default. +{{< /admonition >}} + +Resources saved in the snapshot are strictly limited to the resources stored within an organization. This is important to note if there are multiple organizations used in your Grafana instance. If you want to migrate multiple organizations, refer to [Migrate multiple organizations](https://grafana.com/docs/grafana-cloud/account-management/cloud-migration-assistant/#migrate-multiple-organizations) for more information and guidance. ## Resource migration details @@ -159,7 +167,7 @@ Your data sources, including credentials, are migrated securely and seamlessly t ### Plugins -The migration assistant supports any plugins found in the plugins catalog. As long as the plugin is signed or is a core plugin built into Grafana, it is eligible for migration. Due to security reasons, unsigned plugins are not supported in Grafana Cloud. If you are using any unsigned private plugins, Grafana recommends you seek an alternative plugin for the catalog or work on a strategy to deprecate certain functionality from your self-managed instance. +The migration assistant supports any plugins found in the plugins catalog. As long as the plugin is signed or is a core plugin built into Grafana, it can be migrated. Due to security reasons, unsigned plugins are not supported in Grafana Cloud. If you are using any unsigned private plugins, Grafana recommends you seek an alternative plugin from the catalog or work on a strategy to deprecate certain functionality from your self-managed instance. Upgrade any plugins you intend to migrate before using the migration assistant as any migrated plugins will be configured on the Grafana Cloud instance as the latest version of that plugin. @@ -182,7 +190,13 @@ This is sufficient to have your Alerting configuration up and running in Grafana Migration of Silences is not supported by the migration assistant and needs to be configured manually. Alert History is also not available for migration. -Successfully migrating Alerting resources to your Grafana Cloud instance could result in 2 sets of notifications being generated; one from your OSS/Enterprise instance and another from the newly migrated alerts in your Grafana Cloud instance. To avoid double notifications, a new `alert_rules_state` configuration option in the `custom.ini` or `grafana.ini` file controls how Alert Rules are migrated to the Grafana Cloud instance and is set to `paused` by default so you can review and test your Alerting resources in your Grafana Cloud instance without duplicate notifications. +Successfully migrating Alerting resources to your Grafana Cloud instance could result in 2 sets of notifications being generated: + +1. From your OSS/Enterprise instance + +1. From the newly migrated alerts in your Grafana Cloud instance + +To avoid double notifications, a new `alert_rules_state` configuration option in the `custom.ini` or `grafana.ini` file controls how Alert Rules are migrated to the Grafana Cloud instance and is set to `paused` by default so you can review and test your Alerting resources in your Grafana Cloud instance without duplicate notifications. The available options for `alert_rule_state` are: @@ -200,7 +214,7 @@ Because the migration assistant does not yet migrate teams or RBAC permissions, ## Migrate multiple organizations -If you are using the [organizations](https://grafana.com/docs/grafana/latest/administration/organization-management/#about-organizations) feature on your Grafana Instance and intend to migrate to Grafana Cloud, you need to plan this aspect of the migration carefully. +If you are using the [organizations](https://grafana.com/docs/grafana//administration/organization-management/#about-organizations) feature on your Grafana Instance and intend to migrate to Grafana Cloud, you need to plan this aspect of the migration carefully. The organizations feature is not supported in Grafana Cloud, but folders and RBAC can be used to protect and grant permissions to resources instead. The recommended path is to migrate multiple organizations to a single cloud stack. This is the simplest option and provides the best user experience. @@ -212,4 +226,4 @@ The Grafana server administrator is granted access to the migration assistant by The main driver for setting up organizations in the first place is resource isolation. In order to achieve this in Grafana Cloud, you can organize resources into folders and set up teams and permissions that correspond to your organizations. -For more information about configuring teams and permissions, refer to [Configure Grafana Teams](https://grafana.com/docs/grafana/latest/administration/team-management/configure-grafana-teams/). +For more information about configuring teams and permissions, refer to [Configure Grafana Teams](https://grafana.com/docs/grafana//administration/team-management/configure-grafana-teams/). From 89198bb749c247fa0fd394172bb939c3d79be3da Mon Sep 17 00:00:00 2001 From: ismail simsek Date: Tue, 22 Apr 2025 19:23:10 +0200 Subject: [PATCH 007/146] Prometheus: Remove series endpoint call from Metrics Explorer (#104263) * Remove series endpoint call * lint --- .../src/metric_find_query.test.ts | 31 +++++-------------- .../src/metric_find_query.ts | 12 ++----- 2 files changed, 11 insertions(+), 32 deletions(-) diff --git a/packages/grafana-prometheus/src/metric_find_query.test.ts b/packages/grafana-prometheus/src/metric_find_query.test.ts index 8a0aabe6480..29077107524 100644 --- a/packages/grafana-prometheus/src/metric_find_query.test.ts +++ b/packages/grafana-prometheus/src/metric_find_query.test.ts @@ -147,15 +147,11 @@ describe('PrometheusMetricFindQuery', () => { }); // - it('label_values(metric, resource) should generate series query with correct time', async () => { + it('label_values(metric, resource) should generate label/__name__/values query with correct time', async () => { const query = setupMetricFindQuery({ query: 'label_values(metric, resource)', response: { - data: [ - { __name__: 'metric', resource: 'value1' }, - { __name__: 'metric', resource: 'value2' }, - { __name__: 'metric', resource: 'value3' }, - ], + data: ['value1', 'value2', 'value3'], }, }); const results = await query.process(raw); @@ -164,24 +160,19 @@ describe('PrometheusMetricFindQuery', () => { expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledWith({ method: 'GET', - url: `/api/datasources/uid/ABCDEF/resources/api/v1/series?match${encodeURIComponent( + url: `/api/datasources/uid/ABCDEF/resources/api/v1/label/resource/values?match${encodeURIComponent( '[]' )}=metric&start=${raw.from.unix()}&end=${raw.to.unix()}`, hideFromInspector: true, - showErrorAlert: false, headers: {}, }); }); - it('label_values(metric{label1="foo", label2="bar", label3="baz"}, resource) should generate series query with correct time', async () => { + it('label_values(metric{label1="foo", label2="bar", label3="baz"}, resource) should generate label/__name__/values? query with correct time', async () => { const query = setupMetricFindQuery({ query: 'label_values(metric{label1="foo", label2="bar", label3="baz"}, resource)', response: { - data: [ - { __name__: 'metric', resource: 'value1' }, - { __name__: 'metric', resource: 'value2' }, - { __name__: 'metric', resource: 'value3' }, - ], + data: ['value1', 'value2', 'value3'], }, }); const results = await query.process(raw); @@ -190,9 +181,8 @@ describe('PrometheusMetricFindQuery', () => { expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledWith({ method: 'GET', - url: '/api/datasources/uid/ABCDEF/resources/api/v1/series?match%5B%5D=metric%7Blabel1%3D%22foo%22%2C%20label2%3D%22bar%22%2C%20label3%3D%22baz%22%7D&start=1524650400&end=1524654000', + url: '/api/datasources/uid/ABCDEF/resources/api/v1/label/resource/values?match%5B%5D=metric%7Blabel1%3D%22foo%22%2C%20label2%3D%22bar%22%2C%20label3%3D%22baz%22%7D&start=1524650400&end=1524654000', hideFromInspector: true, - showErrorAlert: false, headers: {}, }); }); @@ -201,11 +191,7 @@ describe('PrometheusMetricFindQuery', () => { const query = setupMetricFindQuery({ query: 'label_values(metric, resource)', response: { - data: [ - { __name__: 'metric', resource: 'value1' }, - { __name__: 'metric', resource: 'value2' }, - { __name__: 'metric', resource: '' }, - ], + data: ['value1', 'value2'], }, }); const results = await query.process(raw); @@ -216,11 +202,10 @@ describe('PrometheusMetricFindQuery', () => { expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledWith({ method: 'GET', - url: `/api/datasources/uid/ABCDEF/resources/api/v1/series?match${encodeURIComponent( + url: `/api/datasources/uid/ABCDEF/resources/api/v1/label/resource/values?match${encodeURIComponent( '[]' )}=metric&start=${raw.from.unix()}&end=${raw.to.unix()}`, hideFromInspector: true, - showErrorAlert: false, headers: {}, }); }); diff --git a/packages/grafana-prometheus/src/metric_find_query.ts b/packages/grafana-prometheus/src/metric_find_query.ts index 12d17e7126f..ff506eb2f51 100644 --- a/packages/grafana-prometheus/src/metric_find_query.ts +++ b/packages/grafana-prometheus/src/metric_find_query.ts @@ -86,23 +86,17 @@ export class PrometheusMetricFindQuery { escapedLabel = escapeForUtf8Support(label); } - if (!metric || this.datasource.hasLabelsMatchAPISupport()) { - const url = `/api/v1/label/${escapedLabel}/values`; + const url = `/api/v1/label/${escapedLabel}/values`; + if (!metric || this.datasource.hasLabelsMatchAPISupport()) { return this.datasource.metadataRequest(url, params).then((result) => { return _map(result.data.data, (value) => { return { text: value }; }); }); } else { - const url = `/api/v1/series`; - return this.datasource.metadataRequest(url, params).then((result) => { - const _labels = _map(result.data.data, (metric) => { - return metric[label] || ''; - }).filter((label) => { - return label !== ''; - }); + const _labels = _map(result.data.data, (metric) => metric); return uniq(_labels).map((metric) => { return { From 4bf32f3651786ca96de6b1a750c95677c52f54a1 Mon Sep 17 00:00:00 2001 From: Eric Leijonmarck Date: Tue, 22 Apr 2025 18:36:47 +0100 Subject: [PATCH 008/146] Zanzana: Adds readme with configuration for openfga cli (#104276) --- pkg/services/authz/zanzana/README.md | 42 ++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 pkg/services/authz/zanzana/README.md diff --git a/pkg/services/authz/zanzana/README.md b/pkg/services/authz/zanzana/README.md new file mode 100644 index 00000000000..d3afff67a58 --- /dev/null +++ b/pkg/services/authz/zanzana/README.md @@ -0,0 +1,42 @@ +# Zanzana + +## Zanzana cli +Zanzana can be run as a standalone OpenFGA HTTP server that allows you to use the OpenFGA CLI to debug and manage fine-grained authorization relationships within Grafana. + +To test this you need to run standalone zanzana server. Use following config: + +```ini +# ini +app_mode = development +target = zanzana-server + +[feature_toggles] +zanzana = true + +[zanzana.server] +http_addr = 127.0.0.1:8080 + +[grpc_server] +enabled = true +address = 127.0.0.1:10000 +``` + +And then run grafana server target: + +```bash +./bin/darwin-arm64/grafana server target +``` + +### Using OpenFGA CLI + +useful info on how to setup and use https://openfga.dev/docs/getting-started/cli +Once the server is running, you can interact with it using the OpenFGA CLI: + +```bash +# List all stores +fga store list + +# Other commands +fga model read +fga tuple list +``` From 4b1fa81394e25a74142f78231565cd9070ba6e03 Mon Sep 17 00:00:00 2001 From: ismail simsek Date: Tue, 22 Apr 2025 19:57:01 +0200 Subject: [PATCH 009/146] Chore: Better builtin variable check during parsing the code (#103952) * replace and return the builtin variables * don't parse the expression twice * improve the replacement logic * better code with more tests * lint * betterer * rename the test suite --- .../src/querybuilder/parsing.ts | 48 ++---------- .../src/querybuilder/parsingUtils.test.ts | 74 ++++++++++++++++++- .../src/querybuilder/parsingUtils.ts | 57 +++++++++++++- 3 files changed, 135 insertions(+), 44 deletions(-) diff --git a/packages/grafana-prometheus/src/querybuilder/parsing.ts b/packages/grafana-prometheus/src/querybuilder/parsing.ts index 42713dfb986..ac3c45d84c6 100644 --- a/packages/grafana-prometheus/src/querybuilder/parsing.ts +++ b/packages/grafana-prometheus/src/querybuilder/parsing.ts @@ -34,7 +34,9 @@ import { getString, makeBinOp, makeError, + replaceBuiltInVariable, replaceVariables, + returnBuiltInVariable, } from './parsingUtils'; import { QueryBuilderLabelFilter, QueryBuilderOperation } from './shared/types'; import { PromVisualQuery, PromVisualQueryBinary } from './types'; @@ -42,12 +44,11 @@ import { PromVisualQuery, PromVisualQueryBinary } from './types'; /** * Parses a PromQL query into a visual query model. * - * It traverses the tree and uses sort of state machine to update the query model. The query model is modified - * during the traversal and sent to each handler as context. - * - * @param expr + * It traverses the tree and uses sort of state machine to update the query model. + * The query model is modified during the traversal and sent to each handler as context. */ export function buildVisualQueryFromString(expr: string): Context { + expr = replaceBuiltInVariable(expr); const replacedExpr = replaceVariables(expr); const tree = parser.parse(replacedExpr); const node = tree.topNode; @@ -80,11 +81,6 @@ export function buildVisualQueryFromString(expr: string): Context { context.errors = []; } - // We don't want parsing errors related to Grafana global variables - if (isValidPromQLMinusGrafanaGlobalVariables(expr)) { - context.errors = []; - } - return context; } @@ -100,36 +96,6 @@ interface Context { errors: ParsingError[]; } -// TODO find a better approach for grafana global variables -function isValidPromQLMinusGrafanaGlobalVariables(expr: string) { - const context: Context = { - query: { - metric: '', - labels: [], - operations: [], - }, - errors: [], - }; - - expr = expr.replace(/\$__interval/g, '1s'); - expr = expr.replace(/\$__interval_ms/g, '1000'); - expr = expr.replace(/\$__rate_interval/g, '1s'); - expr = expr.replace(/\$__range_ms/g, '1000'); - expr = expr.replace(/\$__range_s/g, '1'); - expr = expr.replace(/\$__range/g, '1s'); - - const tree = parser.parse(expr); - const node = tree.topNode; - - try { - handleExpression(expr, node, context); - } catch (err) { - return false; - } - - return context.errors.length === 0; -} - /** * Handler for default state. It will traverse the tree and call the appropriate handler for each node. The node * handled here does not necessarily need to be of type == Expr. @@ -277,7 +243,9 @@ function handleFunction(expr: string, node: SyntaxNode, context: Context) { let match = getString(expr, node).match(/\[(.+)\]/); if (match?.[1]) { interval = match[1]; - params.push(match[1]); + // We were replaced the builtin variables to prevent errors + // Here we return those back + params.push(returnBuiltInVariable(match[1])); } } diff --git a/packages/grafana-prometheus/src/querybuilder/parsingUtils.test.ts b/packages/grafana-prometheus/src/querybuilder/parsingUtils.test.ts index 3bef72e1fd0..b5f9a55a434 100644 --- a/packages/grafana-prometheus/src/querybuilder/parsingUtils.test.ts +++ b/packages/grafana-prometheus/src/querybuilder/parsingUtils.test.ts @@ -1,7 +1,13 @@ // Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/parsingUtils.test.ts import { parser } from '@prometheus-io/lezer-promql'; -import { getLeftMostChild, getString, replaceVariables } from './parsingUtils'; +import { + getLeftMostChild, + getString, + replaceBuiltInVariable, + replaceVariables, + returnBuiltInVariable, +} from './parsingUtils'; describe('getLeftMostChild', () => { it('return left most child', () => { @@ -41,3 +47,69 @@ describe('getString', () => { expect(getString(replaced, tree.topNode)).toBe(expr); }); }); + +describe('builtInTimeVariables', () => { + const testCases = [ + { + expr: 'sum_over_time([[metric_var]]{bar="${app}"}[$__interval])', + expected: 'sum_over_time([[metric_var]]{bar="${app}"}[711_999_999])', + }, + { + expr: 'sum_over_time([[metric_var]]{bar="${app}"}[$__rate_interval])', + expected: 'sum_over_time([[metric_var]]{bar="${app}"}[7999799979997999])', + }, + { + expr: 'sum_over_time([[metric_var]]{bar="${app}"}[$__range_ms])', + expected: 'sum_over_time([[metric_var]]{bar="${app}"}[722_999_999])', + }, + { + expr: 'histogram_quantile(0.95, sum(rate(process_max_fds[$__rate_interval])) by (le)) + rate(process_max_fds[$__interval])', + expected: + 'histogram_quantile(0.95, sum(rate(process_max_fds[7999799979997999])) by (le)) + rate(process_max_fds[711_999_999])', + }, + { + expr: 'rate(http_requests_total{job="api-server"}[$__interval_ms] offset $__interval_ms)', + expected: 'rate(http_requests_total{job="api-server"}[79_999_999_999] offset 79_999_999_999)', + }, + { + expr: 'max_over_time(node_memory_usage[$__range_s])', + expected: 'max_over_time(node_memory_usage[79_299_999])', + }, + { + expr: 'avg_over_time(cpu_usage{env="prod"}[$__range])', + expected: 'avg_over_time(cpu_usage{env="prod"}[799_999])', + }, + { + expr: 'rate(requests[$__interval]) / rate(requests[$__interval] offset $__interval)', + expected: 'rate(requests[711_999_999]) / rate(requests[711_999_999] offset 711_999_999)', + }, + { + expr: 'sum(rate(http_requests_total{status=~"5.."}[$__rate_interval])) / sum(rate(http_requests_total[$__rate_interval])) or vector($__range_ms / $__interval_ms)', + expected: + 'sum(rate(http_requests_total{status=~"5.."}[7999799979997999])) / sum(rate(http_requests_total[7999799979997999])) or vector(722_999_999 / 79_999_999_999)', + }, + { + expr: 'sum(rate(http_requests_total{job="api"}[5m]))', + expected: 'sum(rate(http_requests_total{job="api"}[5m]))', + }, + { + expr: 'max_over_time(rate(cpu{instance="server-01"}[$__interval])[$__range_s:$__interval])', + expected: 'max_over_time(rate(cpu{instance="server-01"}[711_999_999])[79_299_999:711_999_999])', + }, + { + expr: 'rate(cpu[$__interval]) + rate(memory[$__interval_ms]) + rate(disk[$__rate_interval]) + rate(network[$__range]) + rate(io[$__range_s]) + rate(gpu[$__range_ms])', + expected: + 'rate(cpu[711_999_999]) + rate(memory[79_999_999_999]) + rate(disk[7999799979997999]) + rate(network[799_999]) + rate(io[79_299_999]) + rate(gpu[722_999_999])', + }, + ]; + + testCases.forEach((testCase) => { + it(testCase.expr, () => { + const actual1 = replaceBuiltInVariable(testCase.expr); + expect(actual1).toBe(testCase.expected); + + const actual2 = returnBuiltInVariable(actual1); + expect(actual2).toBe(testCase.expr); + }); + }); +}); diff --git a/packages/grafana-prometheus/src/querybuilder/parsingUtils.ts b/packages/grafana-prometheus/src/querybuilder/parsingUtils.ts index 2b9cb162d93..15136e51005 100644 --- a/packages/grafana-prometheus/src/querybuilder/parsingUtils.ts +++ b/packages/grafana-prometheus/src/querybuilder/parsingUtils.ts @@ -32,9 +32,8 @@ export function makeError(expr: string, node: SyntaxNode) { const variableRegex = /\$(\w+)|\[\[([\s\S]+?)(?::(\w+))?\]\]|\${(\w+)(?:\.([^:^\}]+))?(?::([^\}]+))?}/g; /** - * As variables with $ are creating parsing errors, we first replace them with magic string that is parsable and at - * the same time we can get the variable and its format back from it. - * @param expr + * As variables with $ are creating parsing errors, we first replace them with magic string that is + * parsable and at the same time we can get the variable and its format back from it. */ export function replaceVariables(expr: string) { return expr.replace(variableRegex, (match, var1, var2, fmt2, var3, fieldPath, fmt3) => { @@ -138,3 +137,55 @@ export const regexifyLabelValuesQueryString = (query: string) => { const queryArray = query.split(' '); return queryArray.map((query) => `${query}.*`).join(''); }; + +/** + * Built-in Grafana variables used for time ranges and intervals in Prometheus queries + * Each variable has a carefully crafted numeric replacement that: + * 1. Has exactly the same string length as the original variable + * 2. Is valid in Prometheus syntax to avoid parsing errors + * 3. Preserves error position information for accurate error reporting + * 4. Uses readable number formatting with digit grouping via underscores + * https://prometheus.io/docs/prometheus/latest/querying/basics/#float-literals-and-time-durations + */ +const BUILT_IN_VARIABLES = [ + { variable: '$__interval_ms', replacement: '79_999_999_999' }, + { variable: '$__interval', replacement: '711_999_999' }, + { variable: '$__rate_interval', replacement: '7999799979997999' }, + { variable: '$__range_ms', replacement: '722_999_999' }, + { variable: '$__range_s', replacement: '79_299_999' }, + { variable: '$__range', replacement: '799_999' }, +]; + +// Derived maps for efficient lookups +const variableToReplacement = BUILT_IN_VARIABLES.reduce>((map, { variable, replacement }) => { + map[variable] = replacement; + return map; +}, {}); + +const replacementToVariable = BUILT_IN_VARIABLES.reduce>((map, { variable, replacement }) => { + map[replacement] = variable; + return map; +}, {}); + +// Pre-compiled regular expressions for efficient search/replace +const builtInVariablePattern = BUILT_IN_VARIABLES.map(({ variable }) => variable.replace(/\$/g, '\\$')).join('|'); +const builtInVariableRegex = new RegExp(builtInVariablePattern, 'g'); + +const builtInReplacementPattern = BUILT_IN_VARIABLES.map(({ replacement }) => replacement).join('|'); +const builtInReplacementRegex = new RegExp(builtInReplacementPattern, 'g'); + +/** + * Replaces Grafana built-in variables with numeric replacements + * This helps prevent these variables from causing parsing errors + */ +export function replaceBuiltInVariable(expr: string): string { + return expr.replace(builtInVariableRegex, (match) => variableToReplacement[match]); +} + +/** + * Restores the original built-in variables from their replacement format + * Reverses the transformation done by replaceBuiltInVariable + */ +export function returnBuiltInVariable(expr: string): string { + return expr.replace(builtInReplacementRegex, (match) => replacementToVariable[match]); +} From 82332819ef08cd95ed13983fe3836bf3fedfce58 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Tue, 22 Apr 2025 21:34:56 +0300 Subject: [PATCH 010/146] DataSourceSrv: include alias in type filter (#104308) --- .../panel-barchart/barchart-autosizing.json | 30 ++++--------------- public/app/features/plugins/datasource_srv.ts | 10 +++++-- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/devenv/dev-dashboards/panel-barchart/barchart-autosizing.json b/devenv/dev-dashboards/panel-barchart/barchart-autosizing.json index cf261ba8052..d484fabcc11 100644 --- a/devenv/dev-dashboards/panel-barchart/barchart-autosizing.json +++ b/devenv/dev-dashboards/panel-barchart/barchart-autosizing.json @@ -29,10 +29,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -110,10 +107,7 @@ "type": "barchart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "description": "Should be smaller given the longer value", "fieldConfig": { "defaults": { @@ -193,10 +187,7 @@ "type": "barchart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -274,10 +265,7 @@ "type": "barchart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -436,10 +424,7 @@ "type": "barchart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -518,10 +503,7 @@ "type": "barchart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "description": "", "fieldConfig": { "defaults": { diff --git a/public/app/features/plugins/datasource_srv.ts b/public/app/features/plugins/datasource_srv.ts index 15b0fe15bd0..4b5bcda96b8 100644 --- a/public/app/features/plugins/datasource_srv.ts +++ b/public/app/features/plugins/datasource_srv.ts @@ -259,8 +259,14 @@ export class DatasourceSrv implements DataSourceService { if (filters.filter && !filters.filter(x)) { return false; } - if (filters.type && (Array.isArray(filters.type) ? !filters.type.includes(x.type) : filters.type !== x.type)) { - return false; + if (filters.type) { + if (Array.isArray(filters.type)) { + if (!filters.type.includes(x.type)) { + return false; + } + } else if (!(x.type === filters.type || x.meta.aliasIDs?.includes(filters.type!))) { + return false; + } } if ( !filters.all && From 5ddcac8678e4a5f51f32a1db0a7035a312f75e10 Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Tue, 22 Apr 2025 14:57:04 -0400 Subject: [PATCH 011/146] Alerting: Add back time interval items to the API spec (#104306) --- pkg/services/ngalert/api/tooling/api.json | 61 ++++++++++- .../api/tooling/definitions/time_intervals.go | 19 ++++ pkg/services/ngalert/api/tooling/post.json | 63 +++++++++-- pkg/services/ngalert/api/tooling/spec.json | 63 +++++++++-- public/api-enterprise-spec.json | 100 ++++++++++++++++++ public/api-merged.json | 61 ++++++++++- public/openapi3.json | 61 ++++++++++- 7 files changed, 402 insertions(+), 26 deletions(-) create mode 100644 pkg/services/ngalert/api/tooling/definitions/time_intervals.go diff --git a/pkg/services/ngalert/api/tooling/api.json b/pkg/services/ngalert/api/tooling/api.json index 696e5686d06..e6305fc2c91 100644 --- a/pkg/services/ngalert/api/tooling/api.json +++ b/pkg/services/ngalert/api/tooling/api.json @@ -492,6 +492,12 @@ "name": { "type": "string" }, + "queriedDatasourceUIDs": { + "items": { + "type": "string" + }, + "type": "array" + }, "query": { "type": "string" }, @@ -521,9 +527,7 @@ } }, "required": [ - "uid", "name", - "folderUid", "query", "health", "type", @@ -3789,9 +3793,7 @@ } }, "required": [ - "uid", "name", - "folderUid", "query", "health", "type" @@ -4530,6 +4532,55 @@ "title": "TimeInterval represents a named set of time intervals for which a route should be muted.", "type": "object" }, + "TimeIntervalItem": { + "properties": { + "days_of_month": { + "items": { + "type": "string" + }, + "type": "array" + }, + "location": { + "type": "string" + }, + "months": { + "items": { + "type": "string" + }, + "type": "array" + }, + "times": { + "items": { + "$ref": "#/definitions/TimeIntervalTimeRange" + }, + "type": "array" + }, + "weekdays": { + "items": { + "type": "string" + }, + "type": "array" + }, + "years": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "TimeIntervalTimeRange": { + "properties": { + "end_time": { + "type": "string" + }, + "start_time": { + "type": "string" + } + }, + "type": "object" + }, "TimeRange": { "description": "Redefining this to avoid an import cycle", "properties": { @@ -4988,6 +5039,7 @@ "type": "object" }, "gettableAlerts": { + "description": "GettableAlerts gettable alerts", "items": { "$ref": "#/definitions/gettableAlert", "type": "object" @@ -5112,6 +5164,7 @@ "type": "object" }, "gettableSilences": { + "description": "GettableSilences gettable silences", "items": { "$ref": "#/definitions/gettableSilence", "type": "object" diff --git a/pkg/services/ngalert/api/tooling/definitions/time_intervals.go b/pkg/services/ngalert/api/tooling/definitions/time_intervals.go new file mode 100644 index 00000000000..f2709e3fbc2 --- /dev/null +++ b/pkg/services/ngalert/api/tooling/definitions/time_intervals.go @@ -0,0 +1,19 @@ +package definitions + +// NOTE: These structs are needed to support the MuteTimings provisioning api in the open api client + +// swagger:model +type TimeIntervalItem struct { + Times []TimeIntervalTimeRange `json:"times,omitempty" hcl:"times,block"` + Weekdays *[]string `json:"weekdays,omitempty" hcl:"weekdays"` + DaysOfMonth *[]string `json:"days_of_month,omitempty" hcl:"days_of_month"` + Months *[]string `json:"months,omitempty" hcl:"months"` + Years *[]string `json:"years,omitempty" hcl:"years"` + Location *string `json:"location,omitempty" hcl:"location"` +} + +// swagger:model +type TimeIntervalTimeRange struct { + StartMinute string `json:"start_time" hcl:"start"` + EndMinute string `json:"end_time" hcl:"end"` +} diff --git a/pkg/services/ngalert/api/tooling/post.json b/pkg/services/ngalert/api/tooling/post.json index a8c9b24f49e..6f005554994 100644 --- a/pkg/services/ngalert/api/tooling/post.json +++ b/pkg/services/ngalert/api/tooling/post.json @@ -492,6 +492,12 @@ "name": { "type": "string" }, + "queriedDatasourceUIDs": { + "items": { + "type": "string" + }, + "type": "array" + }, "query": { "type": "string" }, @@ -521,9 +527,7 @@ } }, "required": [ - "uid", "name", - "folderUid", "query", "health", "type", @@ -3789,9 +3793,7 @@ } }, "required": [ - "uid", "name", - "folderUid", "query", "health", "type" @@ -4530,6 +4532,55 @@ "title": "TimeInterval represents a named set of time intervals for which a route should be muted.", "type": "object" }, + "TimeIntervalItem": { + "properties": { + "days_of_month": { + "items": { + "type": "string" + }, + "type": "array" + }, + "location": { + "type": "string" + }, + "months": { + "items": { + "type": "string" + }, + "type": "array" + }, + "times": { + "items": { + "$ref": "#/definitions/TimeIntervalTimeRange" + }, + "type": "array" + }, + "weekdays": { + "items": { + "type": "string" + }, + "type": "array" + }, + "years": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "TimeIntervalTimeRange": { + "properties": { + "end_time": { + "type": "string" + }, + "start_time": { + "type": "string" + } + }, + "type": "object" + }, "TimeRange": { "description": "Redefining this to avoid an import cycle", "properties": { @@ -4545,7 +4596,6 @@ "type": "object" }, "URL": { - "description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nThe Host field contains the host and port subcomponents of the URL.\nWhen the port is present, it is separated from the host with a colon.\nWhen the host is an IPv6 address, it must be enclosed in square brackets:\n\"[fe80::1]:80\". The [net.JoinHostPort] function combines a host and port\ninto a string suitable for the Host field, adding square brackets to\nthe host when necessary.\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use the [URL.EscapedPath] method, which preserves\nthe original encoding of Path.\n\nThe RawPath field is an optional field which is only set when the default\nencoding of Path is different from the escaped path. See the EscapedPath method\nfor more details.\n\nURL's String method uses the EscapedPath method to obtain the path.", "properties": { "ForceQuery": { "type": "boolean" @@ -4581,7 +4631,7 @@ "$ref": "#/definitions/Userinfo" } }, - "title": "A URL represents a parsed URL (technically, a URI reference).", + "title": "URL is a custom URL type that allows validation at configuration load time.", "type": "object" }, "UpdateRuleGroupResponse": { @@ -5112,7 +5162,6 @@ "type": "object" }, "gettableSilences": { - "description": "GettableSilences gettable silences", "items": { "$ref": "#/definitions/gettableSilence", "type": "object" diff --git a/pkg/services/ngalert/api/tooling/spec.json b/pkg/services/ngalert/api/tooling/spec.json index 1875f799b8b..bc9e62927f7 100644 --- a/pkg/services/ngalert/api/tooling/spec.json +++ b/pkg/services/ngalert/api/tooling/spec.json @@ -4547,9 +4547,7 @@ "description": "adapted from cortex", "type": "object", "required": [ - "uid", "name", - "folderUid", "query", "health", "type", @@ -4602,6 +4600,12 @@ "name": { "type": "string" }, + "queriedDatasourceUIDs": { + "type": "array", + "items": { + "type": "string" + } + }, "query": { "type": "string" }, @@ -7856,9 +7860,7 @@ "description": "adapted from cortex", "type": "object", "required": [ - "uid", "name", - "folderUid", "query", "health", "type" @@ -8630,6 +8632,55 @@ } } }, + "TimeIntervalItem": { + "type": "object", + "properties": { + "days_of_month": { + "type": "array", + "items": { + "type": "string" + } + }, + "location": { + "type": "string" + }, + "months": { + "type": "array", + "items": { + "type": "string" + } + }, + "times": { + "type": "array", + "items": { + "$ref": "#/definitions/TimeIntervalTimeRange" + } + }, + "weekdays": { + "type": "array", + "items": { + "type": "string" + } + }, + "years": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "TimeIntervalTimeRange": { + "type": "object", + "properties": { + "end_time": { + "type": "string" + }, + "start_time": { + "type": "string" + } + } + }, "TimeRange": { "description": "Redefining this to avoid an import cycle", "type": "object", @@ -8645,9 +8696,8 @@ } }, "URL": { - "description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nThe Host field contains the host and port subcomponents of the URL.\nWhen the port is present, it is separated from the host with a colon.\nWhen the host is an IPv6 address, it must be enclosed in square brackets:\n\"[fe80::1]:80\". The [net.JoinHostPort] function combines a host and port\ninto a string suitable for the Host field, adding square brackets to\nthe host when necessary.\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use the [URL.EscapedPath] method, which preserves\nthe original encoding of Path.\n\nThe RawPath field is an optional field which is only set when the default\nencoding of Path is different from the escaped path. See the EscapedPath method\nfor more details.\n\nURL's String method uses the EscapedPath method to obtain the path.", "type": "object", - "title": "A URL represents a parsed URL (technically, a URI reference).", + "title": "URL is a custom URL type that allows validation at configuration load time.", "properties": { "ForceQuery": { "type": "boolean" @@ -9212,7 +9262,6 @@ } }, "gettableSilences": { - "description": "GettableSilences gettable silences", "type": "array", "items": { "type": "object", diff --git a/public/api-enterprise-spec.json b/public/api-enterprise-spec.json index cc8046467ec..fdc686ac78f 100644 --- a/public/api-enterprise-spec.json +++ b/public/api-enterprise-spec.json @@ -3897,6 +3897,30 @@ } } }, + "CreateSnapshotRequestDTO": { + "type": "object", + "properties": { + "resourceTypes": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "DASHBOARD", + "DATASOURCE", + "FOLDER", + "LIBRARY_ELEMENT", + "ALERT_RULE", + "ALERT_RULE_GROUP", + "CONTACT_POINT", + "NOTIFICATION_POLICY", + "NOTIFICATION_TEMPLATE", + "MUTE_TIMING", + "PLUGIN" + ] + } + } + } + }, "CreateSnapshotResponseDTO": { "type": "object", "properties": { @@ -6038,6 +6062,9 @@ "language": { "type": "string" }, + "locale": { + "type": "string" + }, "navbar": { "$ref": "#/definitions/NavbarPreference" }, @@ -6304,6 +6331,10 @@ "description": "Selected language (beta)", "type": "string" }, + "locale": { + "description": "Selected locale (beta)", + "type": "string" + }, "navbar": { "$ref": "#/definitions/NavbarPreference" }, @@ -6942,6 +6973,57 @@ } } }, + "ResourceDependenciesResponseDTO": { + "type": "object", + "properties": { + "resourceDependencies": { + "type": "array", + "items": { + "$ref": "#/definitions/ResourceDependencyDTO" + } + } + } + }, + "ResourceDependencyDTO": { + "type": "object", + "properties": { + "dependencies": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "DASHBOARD", + "DATASOURCE", + "FOLDER", + "LIBRARY_ELEMENT", + "ALERT_RULE", + "ALERT_RULE_GROUP", + "CONTACT_POINT", + "NOTIFICATION_POLICY", + "NOTIFICATION_TEMPLATE", + "MUTE_TIMING", + "PLUGIN" + ] + } + }, + "resourceType": { + "type": "string", + "enum": [ + "DASHBOARD", + "DATASOURCE", + "FOLDER", + "LIBRARY_ELEMENT", + "ALERT_RULE", + "ALERT_RULE_GROUP", + "CONTACT_POINT", + "NOTIFICATION_POLICY", + "NOTIFICATION_TEMPLATE", + "MUTE_TIMING", + "PLUGIN" + ] + } + } + }, "Responses": { "description": "The QueryData method the QueryDataHandler method will set the RefId\nproperty on the DataResponses' frames based on these RefIDs.", "type": "object", @@ -7682,10 +7764,16 @@ "email": { "type": "string" }, + "externalUID": { + "type": "string" + }, "id": { "type": "integer", "format": "int64" }, + "isProvisioned": { + "type": "boolean" + }, "memberCount": { "type": "integer", "format": "int64" @@ -7798,6 +7886,9 @@ "userId": { "type": "integer", "format": "int64" + }, + "userUID": { + "type": "string" } } }, @@ -8379,6 +8470,9 @@ "language": { "type": "string" }, + "locale": { + "type": "string" + }, "navbar": { "$ref": "#/definitions/NavbarPreference" }, @@ -10154,6 +10248,12 @@ "$ref": "#/definitions/ActiveUserStats" } }, + "resourceDependenciesResponse": { + "description": "", + "schema": { + "$ref": "#/definitions/ResourceDependenciesResponseDTO" + } + }, "resourcePermissionsDescription": { "description": "", "schema": { diff --git a/public/api-merged.json b/public/api-merged.json index 0d448848112..aa6563b5c1c 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -12858,9 +12858,7 @@ "description": "adapted from cortex", "type": "object", "required": [ - "uid", "name", - "folderUid", "query", "health", "type", @@ -12913,6 +12911,12 @@ "name": { "type": "string" }, + "queriedDatasourceUIDs": { + "type": "array", + "items": { + "type": "string" + } + }, "query": { "type": "string" }, @@ -20132,9 +20136,7 @@ "description": "adapted from cortex", "type": "object", "required": [ - "uid", "name", - "folderUid", "query", "health", "type" @@ -21684,6 +21686,55 @@ } } }, + "TimeIntervalItem": { + "type": "object", + "properties": { + "days_of_month": { + "type": "array", + "items": { + "type": "string" + } + }, + "location": { + "type": "string" + }, + "months": { + "type": "array", + "items": { + "type": "string" + } + }, + "times": { + "type": "array", + "items": { + "$ref": "#/definitions/TimeIntervalTimeRange" + } + }, + "weekdays": { + "type": "array", + "items": { + "type": "string" + } + }, + "years": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "TimeIntervalTimeRange": { + "type": "object", + "properties": { + "end_time": { + "type": "string" + }, + "start_time": { + "type": "string" + } + } + }, "TimeRange": { "description": "Redefining this to avoid an import cycle", "type": "object", @@ -22973,6 +23024,7 @@ } }, "gettableAlerts": { + "description": "GettableAlerts gettable alerts", "type": "array", "items": { "type": "object", @@ -23097,6 +23149,7 @@ } }, "gettableSilences": { + "description": "GettableSilences gettable silences", "type": "array", "items": { "type": "object", diff --git a/public/openapi3.json b/public/openapi3.json index 562042acc6d..fe5310802bb 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -2963,6 +2963,12 @@ "name": { "type": "string" }, + "queriedDatasourceUIDs": { + "items": { + "type": "string" + }, + "type": "array" + }, "query": { "type": "string" }, @@ -2992,9 +2998,7 @@ } }, "required": [ - "uid", "name", - "folderUid", "query", "health", "type", @@ -10227,9 +10231,7 @@ } }, "required": [ - "uid", "name", - "folderUid", "query", "health", "type" @@ -11745,6 +11747,55 @@ "title": "TimeInterval represents a named set of time intervals for which a route should be muted.", "type": "object" }, + "TimeIntervalItem": { + "properties": { + "days_of_month": { + "items": { + "type": "string" + }, + "type": "array" + }, + "location": { + "type": "string" + }, + "months": { + "items": { + "type": "string" + }, + "type": "array" + }, + "times": { + "items": { + "$ref": "#/components/schemas/TimeIntervalTimeRange" + }, + "type": "array" + }, + "weekdays": { + "items": { + "type": "string" + }, + "type": "array" + }, + "years": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "TimeIntervalTimeRange": { + "properties": { + "end_time": { + "type": "string" + }, + "start_time": { + "type": "string" + } + }, + "type": "object" + }, "TimeRange": { "description": "Redefining this to avoid an import cycle", "properties": { @@ -13033,6 +13084,7 @@ "type": "object" }, "gettableAlerts": { + "description": "GettableAlerts gettable alerts", "items": { "$ref": "#/components/schemas/gettableAlert" }, @@ -13156,6 +13208,7 @@ "type": "object" }, "gettableSilences": { + "description": "GettableSilences gettable silences", "items": { "$ref": "#/components/schemas/gettableSilence" }, From 2eab7cb63b7127f5acf02d788800d018fbbd3847 Mon Sep 17 00:00:00 2001 From: Adam Simpson Date: Tue, 22 Apr 2025 15:20:45 -0400 Subject: [PATCH 012/146] ds-querier: improve instant vector support (#103954) --- pkg/registry/apis/query/query.go | 14 +++---- pkg/registry/apis/query/query_test.go | 56 +++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/pkg/registry/apis/query/query.go b/pkg/registry/apis/query/query.go index da73b222fab..4eaa024c4f6 100644 --- a/pkg/registry/apis/query/query.go +++ b/pkg/registry/apis/query/query.go @@ -191,9 +191,9 @@ func (b *QueryAPIBuilder) execute(ctx context.Context, req parsedRequestInfo) (q case 1: b.log.Debug("executing single query") qdr, err = b.handleQuerySingleDatasource(ctx, req.Requests[0]) - if err == nil && alertQueryWithoutExpression(req) { - b.log.Debug("handling alert query without expression") - qdr, err = b.convertQueryWithoutExpression(ctx, req.Requests[0], qdr) + if err == nil && isSingleAlertQuery(req) { + b.log.Debug("handling alert query with single query") + qdr, err = b.convertQueryFromAlerting(ctx, req.Requests[0], qdr) } default: b.log.Debug("executing concurrent queries") @@ -416,7 +416,7 @@ func (b *QueryAPIBuilder) handleExpressions(ctx context.Context, req parsedReque return qdr, nil } -func (b *QueryAPIBuilder) convertQueryWithoutExpression(ctx context.Context, req datasourceRequest, +func (b *QueryAPIBuilder) convertQueryFromAlerting(ctx context.Context, req datasourceRequest, qdr *backend.QueryDataResponse) (*backend.QueryDataResponse, error) { if len(req.Request.Queries) == 0 { return nil, errors.New("no queries to convert") @@ -475,14 +475,14 @@ func (r responderWrapper) Error(err error) { r.wrapped.Error(err) } -// Checks if the request only contains a single query and not expression. -func alertQueryWithoutExpression(req parsedRequestInfo) bool { +// Checks if the request only contains a single query and is from Alerting +func isSingleAlertQuery(req parsedRequestInfo) bool { if len(req.Requests) != 1 { return false } headers := req.Requests[0].Headers _, exist := headers[models.FromAlertHeaderName] - if exist && len(req.Requests[0].Request.Queries) == 1 && len(req.Expressions) == 0 { + if exist && len(req.Requests[0].Request.Queries) == 1 { return true } return false diff --git a/pkg/registry/apis/query/query_test.go b/pkg/registry/apis/query/query_test.go index 5147b51bfb9..fbe1a2766e7 100644 --- a/pkg/registry/apis/query/query_test.go +++ b/pkg/registry/apis/query/query_test.go @@ -7,8 +7,10 @@ import ( "net/http" "net/http/httptest" "testing" + "time" "github.com/grafana/grafana-plugin-sdk-go/backend" + frameData "github.com/grafana/grafana-plugin-sdk-go/data" data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" "github.com/grafana/grafana/pkg/expr" "github.com/grafana/grafana/pkg/infra/log" @@ -81,6 +83,60 @@ func TestQueryRestConnectHandler(t *testing.T) { }, *b.client.(mockClient).lastCalledWithHeaders) } +func TestInstantQueryFromAlerting(t *testing.T) { + builder := &QueryAPIBuilder{ + converter: &expr.ResultConverter{ + Features: featuremgmt.WithFeatures(), + Tracer: tracing.InitializeTracerForTest(), + }, + } + + dq := data.DataQuery{} + dq.RefID = "A" + + dr := datasourceRequest{ + Headers: map[string]string{ + models.FromAlertHeaderName: "true", + }, + Request: &data.QueryDataRequest{ + Queries: []data.DataQuery{ + dq, + }, + }, + } + + fakeFrame := frameData.NewFrame( + "A", + frameData.NewField("Time", nil, []time.Time{time.Now()}), + frameData.NewField("Value", nil, []int64{42}), + ) + fakeFrame.Meta = &frameData.FrameMeta{TypeVersion: frameData.FrameTypeVersion{0, 1}, Type: "numeric-multi"} + + inputQDR := &backend.QueryDataResponse{ + Responses: map[string]backend.DataResponse{ + "A": { + Frames: frameData.Frames{ + fakeFrame, + }, + }, + }, + } + + request := parsedRequestInfo{ + Requests: []datasourceRequest{ + dr, + }, + } + + result, err := builder.convertQueryFromAlerting(context.Background(), dr, inputQDR) + require.NoError(t, err) + + require.True(t, isSingleAlertQuery(request), "Expected a valid alert query with a single query to return true") + require.NotNil(t, result) + require.Equal(t, 1, len(result.Responses["A"].Frames[0].Fields), "Expected a single field not Time and Value") + require.Equal(t, "Value", result.Responses["A"].Frames[0].Fields[0].Name, "Expected the single field to be Value") +} + type mockResponder struct { } From 8dd5dbbe5c14234476d4cc2c30d62a8105dc0ab1 Mon Sep 17 00:00:00 2001 From: Marie Cruz Date: Tue, 22 Apr 2025 20:26:34 +0100 Subject: [PATCH 013/146] Dashboard: remove broken play dashboard on the annotate visualizations page (#104288) remove broken play dashboard --- .../build-dashboards/annotate-visualizations/index.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/sources/dashboards/build-dashboards/annotate-visualizations/index.md b/docs/sources/dashboards/build-dashboards/annotate-visualizations/index.md index 610f74585d1..7b7f90c097d 100644 --- a/docs/sources/dashboards/build-dashboards/annotate-visualizations/index.md +++ b/docs/sources/dashboards/build-dashboards/annotate-visualizations/index.md @@ -35,8 +35,6 @@ Annotations provide a way to mark points on a visualization with rich events. Th {{< figure src="/static/img/docs/v46/annotations.png" max-width="800px" alt="Annotated visualization with annotation context menu open" >}} -{{< docs/play title="Annotations" url="https://play.grafana.org/d/000000010/" >}} - You can annotate visualizations in three ways: - Directly in the panel, using the [built-in annotations query](#built-in-query) From e8f5200a326ef3572cbf9bb2abf98a7e334d3316 Mon Sep 17 00:00:00 2001 From: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com> Date: Tue, 22 Apr 2025 13:31:06 -0600 Subject: [PATCH 014/146] Chore: Update 10.4.17-sec changelog with cves (#104333) baldm0mma/update 10.4.17-sec changelog with cves --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 711dab275ce..6933a90fcc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - **Alerting:** Update slack image upload to use new API [#101483](https://github.com/grafana/grafana/pull/101483), [@moustafab](https://github.com/moustafab) - **Service Accounts:** Do not show error pop-ups for Service Account and Renderer UI flows [#101804](https://github.com/grafana/grafana/pull/101804), [@IevaVasiljeva](https://github.com/IevaVasiljeva) +- **Security:** Fix CVE-2025-3454 From d80d0304908fee9231c2f14d35b59879c9df74c8 Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 19:36:02 +0000 Subject: [PATCH 015/146] Release: update changelog for 11.3.5+security-01 (#104323) * Update changelog * baldm0mma/ add cves * baldm0mma/ fix syntax * baldm0mma/ fix cve number --------- Co-authored-by: github-actions[bot] Co-authored-by: jev forsberg --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6933a90fcc7..862a680e2f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ + + +# 11.3.5+security-01 (2025-04-22) + +### Features and enhancements + +- **Chore:** Bump Go to 1.23.7 [#101583](https://github.com/grafana/grafana/pull/101583), [@macabu](https://github.com/macabu) +- **Chore:** Bump Go to 1.23.7 (Enterprise) +- **Chore:** Update libs with CVE in dependencies [#102710](https://github.com/grafana/grafana/pull/102710), [@grambbledook](https://github.com/grambbledook) + +### Bug fixes + +- **Alerting:** Fix token-based Slack image upload to work with channel names [#101488](https://github.com/grafana/grafana/pull/101488), [@moustafab](https://github.com/moustafab) +- **Service Accounts:** Do not show error pop-ups for Service Account and Renderer UI flows [#101791](https://github.com/grafana/grafana/pull/101791), [@IevaVasiljeva](https://github.com/IevaVasiljeva) +- **Security:** Fix CVE-2025-3454 +- **Security:** Fix CVE-2025-2703 + + # 10.4.17+security-01 (2025-04-22) From 43f7e8a7df97b3562fd0eb13e746b54e2447e4f1 Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 19:59:25 +0000 Subject: [PATCH 016/146] Release: update changelog for 11.2.8+security-01 (#104317) * Update changelog * baldm0mma/ add cves * baldm0mma/ fix syntax * baldm0mma/ fix cve number --------- Co-authored-by: github-actions[bot] Co-authored-by: jev forsberg --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 862a680e2f8..6ed42a76a1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,24 @@ - **Security:** Fix CVE-2025-2703 + + +# 11.2.8+security-01 (2025-04-22) + +### Features and enhancements + +- **Chore:** Bump Go version to 1.23.7 [#101294](https://github.com/grafana/grafana/pull/101294), [@macabu](https://github.com/macabu) +- **Chore:** Bump Go version to 1.23.7 (Enterprise) + +### Bug fixes + +- **Alerting:** Update slack image upload to use new API [#101487](https://github.com/grafana/grafana/pull/101487), [@moustafab](https://github.com/moustafab) +- **CloudMigrations:** Fix OrderBy clause in GetSnapshotList sql handler [#102351](https://github.com/grafana/grafana/pull/102351), [@mmandrus](https://github.com/mmandrus) +- **Service Accounts:** Do not show error pop-ups for Service Account and Renderer UI flows [#101795](https://github.com/grafana/grafana/pull/101795), [@IevaVasiljeva](https://github.com/IevaVasiljeva) +- **Security:** Fix CVE-2025-3454 +- **Security:** Fix CVE-2025-2703 + + # 10.4.17+security-01 (2025-04-22) From 0d0981ac57aad99e48408da45dc5a649d404d30f Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 19:59:55 +0000 Subject: [PATCH 017/146] Release: update changelog for 11.4.3+security-01 (#104337) * Update changelog * baldm0mma/ update cves in changelog * baldm0mma/ fix cve number --------- Co-authored-by: github-actions[bot] Co-authored-by: jev forsberg --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ed42a76a1c..02cf2ea7dfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ + + +# 11.4.3+security-01 (2025-04-22) + +### Features and enhancements + +- **Chore:** Bump Go to 1.23.7 [#101582](https://github.com/grafana/grafana/pull/101582), [@macabu](https://github.com/macabu) +- **Chore:** Bump Go to 1.23.7 (Enterprise) +- **Chore:** Update CVE-affected golang-gwt dependencies [#102704](https://github.com/grafana/grafana/pull/102704), [@grambbledook](https://github.com/grambbledook) + +### Bug fixes + +- **Alerting:** Fix token-based Slack image upload to work with channel names [#101072](https://github.com/grafana/grafana/pull/101072), [@JacobsonMT](https://github.com/JacobsonMT) +- **InfluxDB:** Improve handling of template variables contained in regular expressions (InfluxQL) [#100987](https://github.com/grafana/grafana/pull/100987), [@aangelisc](https://github.com/aangelisc) +- **Service Accounts:** Do not show error pop-ups for Service Account and Renderer UI flows [#101790](https://github.com/grafana/grafana/pull/101790), [@IevaVasiljeva](https://github.com/IevaVasiljeva) +- **Security:** Fix CVE-2025-3454 +- **Security:** Fix CVE-2025-2703 + + # 11.3.5+security-01 (2025-04-22) From 9e4980320af5ccc6d284655757fa9a760121abee Mon Sep 17 00:00:00 2001 From: Pepe Cano <825430+ppcano@users.noreply.github.com> Date: Tue, 22 Apr 2025 22:14:50 +0200 Subject: [PATCH 018/146] docs(alerting): add JSON payload example to template examples list (#104282) --- .../integrations/webhook-notifier.md | 36 +---------- .../template-notifications/examples.md | 59 +++++++++++++++++++ .../alerts/example-custom-json-payload.md | 44 ++++++++++++++ 3 files changed, 104 insertions(+), 35 deletions(-) create mode 100644 docs/sources/shared/alerts/example-custom-json-payload.md diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md index 898b2ae98b5..1730fca9bb5 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md @@ -284,38 +284,4 @@ For detailed information about these and other template functions, refer to [not Example using JSON helper functions: -``` -{{ define "webhook.custom.payload" -}} - {{ coll.Dict - "receiver" .Receiver - "status" .Status - "alerts" (tmpl.Exec "webhook.custom.simple_alerts" .Alerts | data.JSON) - "groupLabels" .GroupLabels - "commonLabels" .CommonLabels - "commonAnnotations" .CommonAnnotations - "externalURL" .ExternalURL - "version" "1" - "orgId" (index .Alerts 0).OrgID - "truncatedAlerts" .TruncatedAlerts - "groupKey" .GroupKey - "state" (tmpl.Inline "{{ if eq .Status \"resolved\" }}ok{{ else }}alerting{{ end }}" . ) - "allVariables" .Vars - "title" (tmpl.Exec "default.title" . ) - "message" (tmpl.Exec "default.message" . ) - | data.ToJSONPretty " "}} -{{- end }} - -{{- /* Embed json templates in other json templates. */ -}} -{{ define "webhook.custom.simple_alerts" -}} - {{- $alerts := coll.Slice -}} - {{- range . -}} - {{ $alerts = coll.Append (coll.Dict - "status" .Status - "labels" .Labels - "startsAt" .StartsAt - "endsAt" .EndsAt - ) $alerts}} - {{- end -}} - {{- $alerts | data.ToJSON -}} -{{- end }} -``` +{{< docs/shared lookup="alerts/example-custom-json-payload.md" source="grafana" version="" >}} diff --git a/docs/sources/alerting/configure-notifications/template-notifications/examples.md b/docs/sources/alerting/configure-notifications/template-notifications/examples.md index d9ce3f780a0..7e916c5f60a 100644 --- a/docs/sources/alerting/configure-notifications/template-notifications/examples.md +++ b/docs/sources/alerting/configure-notifications/template-notifications/examples.md @@ -55,6 +55,11 @@ refs: destination: /docs/grafana//alerting/fundamentals/notifications/group-alert-notifications/ - pattern: /docs/grafana-cloud/ destination: /docs/grafana-cloud/alerting-and-irm/alerting/fundamentals/notifications/group-alert-notifications/ + custom-payload-webhook: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier/#custom-payload + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier/#custom-payload --- # Notification template examples @@ -402,3 +407,57 @@ Execute the template by passing the dot (`.`): ```template_output [FIRING:1, RESOLVED:1] api warning (sql_db) ``` + +## Custom JSON payload + +The [custom payload option](ref:custom-payload-webhook) in the webhook contact point allows you to customize the payload of webhook notifications using a custom template. + +The following example generates a custom JSON payload by executing other templates with `tmpl.Exec`, and using functions like `coll.Dict` and `data.ToJSON` to process and format JSON data. + +{{< docs/shared lookup="alerts/example-custom-json-payload.md" source="grafana" version="" >}} + +```template_output +{ + "alerts": [ + { + "endsAt": "0001-01-01T00:00:00Z", + "labels": { + "alertname": "InstanceDown", + "grafana_folder": "Test Folder", + "instance": "instance1" + }, + "startsAt": "2025-04-21T10:19:46.179Z", + "status": "firing" + }, + { + "endsAt": "2025-04-22T10:19:46.179Z", + "labels": { + "alertname": "CpuUsage", + "grafana_folder": "Test Folder", + "instance": "instance1" + }, + "startsAt": "2025-04-22T06:19:46.179Z", + "status": "resolved" + } + ], + "allVariables": {}, + "commonAnnotations": {}, + "commonLabels": { + "grafana_folder": "Test Folder", + "instance": "instance1" + }, + "externalURL": "http://localhost:3000/", + "groupKey": "", + "groupLabels": { + "group_label": "group_label_value" + }, + "message": "**Firing**\n\nValue: B=22, C=1\nLabels:\n - alertname = InstanceDown\n - grafana_folder = Test Folder\n - instance = instance1\nAnnotations:\n - summary = Instance instance1 has been down for more than 5 minutes\nSource: http://grafana.com/alerting/grafana/cdeqmlhvflz40f/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana\u0026matcher=alertname%3DInstanceDown\u0026matcher=grafana_folder%3DTest+Folder\u0026matcher=instance%3Dinstance1\u0026orgId=1\nDashboard: http://localhost:3000/d/dashboard_uid?from=1745227186179\u0026orgId=1\u0026to=1745317189058\nPanel: http://localhost:3000/d/dashboard_uid?from=1745227186179\u0026orgId=1\u0026to=1745317189058\u0026viewPanel=1\n\n\n**Resolved**\n\nValue: B=22, C=1\nLabels:\n - alertname = CpuUsage\n - grafana_folder = Test Folder\n - instance = instance1\nAnnotations:\n - summary = CPU usage above 90%\nSource: http://grafana.com/alerting/grafana/oZSMdGj7z/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana\u0026matcher=alertname%3DCpuUsage\u0026matcher=grafana_folder%3DTest+Folder\u0026matcher=instance%3Dinstance1\u0026orgId=1\nDashboard: http://localhost:3000/d/dashboard_uid?from=1745299186179\u0026orgId=1\u0026to=1745317186179\nPanel: http://localhost:3000/d/dashboard_uid?from=1745299186179\u0026orgId=1\u0026to=1745317186179\u0026viewPanel=1\n", + "orgId": 1, + "receiver": "TestReceiver", + "state": "alerting", + "status": "firing", + "title": "[FIRING:1, RESOLVED:1] group_label_value (Test Folder instance1)", + "truncatedAlerts": null, + "version": "1" +} +``` diff --git a/docs/sources/shared/alerts/example-custom-json-payload.md b/docs/sources/shared/alerts/example-custom-json-payload.md new file mode 100644 index 00000000000..cebf6183fa5 --- /dev/null +++ b/docs/sources/shared/alerts/example-custom-json-payload.md @@ -0,0 +1,44 @@ +--- +labels: + products: + - cloud + - enterprise + - oss +title: 'Custom webhook example' +--- + +```go +{{ define "webhook.custom.payload" -}} + {{ coll.Dict + "receiver" .Receiver + "status" .Status + "alerts" (tmpl.Exec "webhook.custom.simple_alerts" .Alerts | data.JSON) + "groupLabels" .GroupLabels + "commonLabels" .CommonLabels + "commonAnnotations" .CommonAnnotations + "externalURL" .ExternalURL + "version" "1" + "orgId" (index .Alerts 0).OrgID + "truncatedAlerts" .TruncatedAlerts + "groupKey" .GroupKey + "state" (tmpl.Inline "{{ if eq .Status \"resolved\" }}ok{{ else }}alerting{{ end }}" . ) + "allVariables" .Vars + "title" (tmpl.Exec "default.title" . ) + "message" (tmpl.Exec "default.message" . ) + | data.ToJSONPretty " "}} +{{- end }} + +{{- /* Embed json templates in other json templates. */ -}} +{{ define "webhook.custom.simple_alerts" -}} + {{- $alerts := coll.Slice -}} + {{- range . -}} + {{ $alerts = coll.Append (coll.Dict + "status" .Status + "labels" .Labels + "startsAt" .StartsAt + "endsAt" .EndsAt + ) $alerts}} + {{- end -}} + {{- $alerts | data.ToJSON -}} +{{- end }} +``` From 7c0cb1b7bb98a9be9a5cd54f7e83b21ff5da3a76 Mon Sep 17 00:00:00 2001 From: Pepe Cano <825430+ppcano@users.noreply.github.com> Date: Tue, 22 Apr 2025 22:15:07 +0200 Subject: [PATCH 019/146] docs(alerting): replace outdated alert state diagram with updated image (#104232) --- docs/sources/alerting/monitor-status/view-alert-state.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/alerting/monitor-status/view-alert-state.md b/docs/sources/alerting/monitor-status/view-alert-state.md index 1c92f5ac7cd..7725361f9de 100644 --- a/docs/sources/alerting/monitor-status/view-alert-state.md +++ b/docs/sources/alerting/monitor-status/view-alert-state.md @@ -61,7 +61,7 @@ refs: An alert rule and its corresponding alert instances can transition through distinct states during the alert rule evaluation. -{{< figure src="/media/docs/alerting/alert-instance-states-v3.png" alt="A diagram of the distinct alert instance states and transitions." max-width="750px" >}} +{{< figure src="/media/docs/alerting/alert-state-diagram2.png" alt="A diagram of the distinct alert instance states and transitions." max-width="750px" >}} There are three key components that helps us understand the behavior of our alerts: From 54fb34f59b2a061a68f3105ddc48cc54e2c67f33 Mon Sep 17 00:00:00 2001 From: Pepe Cano <825430+ppcano@users.noreply.github.com> Date: Tue, 22 Apr 2025 22:17:45 +0200 Subject: [PATCH 020/146] docs(alerting): specify sequential evaluation for imported DS rules (#104234) --- .../alert-rule-evaluation/_index.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/docs/sources/alerting/fundamentals/alert-rule-evaluation/_index.md b/docs/sources/alerting/fundamentals/alert-rule-evaluation/_index.md index bc610c4457f..24e71878bc7 100644 --- a/docs/sources/alerting/fundamentals/alert-rule-evaluation/_index.md +++ b/docs/sources/alerting/fundamentals/alert-rule-evaluation/_index.md @@ -20,6 +20,11 @@ refs: destination: /docs/grafana//alerting/fundamentals/alert-rule-evaluation/state-and-health/ - pattern: /docs/grafana-cloud/ destination: /docs/grafana-cloud/alerting-and-irm/alerting/fundamentals/alert-rule-evaluation/state-and-health/ + import-ds-rules: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/alerting-rules/alerting-migration/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/alerting-rules/alerting-migration/ --- # Alert rule evaluation @@ -33,17 +38,19 @@ The criteria determining when an alert rule fires are based on two settings: ## Evaluation group -Every alert rule is assigned to an evaluation group. You can assign the alert rule to an existing evaluation group or create a new one. +Every alert rule and recording rule is assigned to an evaluation group. You can assign the rule to an existing evaluation group or create a new one. -Each evaluation group contains an **evaluation interval** that determines how frequently the alert rule is checked. For instance, the evaluation may occur every `10s`, `30s`, `1m`, `10m`, etc. +Each evaluation group contains an **evaluation interval** that determines how frequently the rule is checked. For instance, the evaluation may occur every `10s`, `30s`, `1m`, `10m`, etc. **Evaluation strategies** -Alert rules in different groups can be evaluated simultaneously. +Rules in different groups can be evaluated simultaneously. -- **Grafana-managed** alert rules within the same group are evaluated concurrently—they are evaluated at different times over the same evaluation interval but display the same evaluation timestamp. +- **Grafana-managed** rules within the same group are evaluated concurrently—they are evaluated at different times over the same evaluation interval but display the same evaluation timestamp. -- **Data-source managed** alert rules within the same group are evaluated sequentially, one after the other—this is useful to ensure that recording rules are evaluated before alert rules. +- **Data source-managed** rules within the same group are evaluated sequentially, one after the other—this is useful to ensure that recording rules are evaluated before alert rules. + +- **Grafana-managed rules [imported from data source-managed rules](ref:import-ds-rules)** are evaluated sequentially, like data source-managed rules. ## Pending period From 50e24d66341fb2e281500b1c8cee0e5842b4e785 Mon Sep 17 00:00:00 2001 From: Pepe Cano <825430+ppcano@users.noreply.github.com> Date: Tue, 22 Apr 2025 22:19:28 +0200 Subject: [PATCH 021/146] docs(alerting): clarify that webhook extra headers can override the Content-Type header (#104324) --- .../integrations/webhook-notifier.md | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md index 1730fca9bb5..726b9d9cef5 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md @@ -68,17 +68,17 @@ For more details on contact points, including how to test them and enable notifi #### Optional settings -| Option | Description | -| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -| HTTP Method | Specifies the HTTP method to use: `POST` or `PUT`. | -| Basic Authentication Username | Username for HTTP Basic Authentication. | -| Basic Authentication Password | Password for HTTP Basic Authentication. | -| Authentication Header Scheme | Scheme for the `Authorization` Request Header. Default is `Bearer`. | -| Authentication Header Credentials | Credentials for the `Authorization` Request header. | -| Extra Headers | Additional HTTP headers to include in the request. | -| Max Alerts | Maximum number of alerts to include in a notification. Any alerts exceeding this limit are ignored. `0` means no limit. | -| TLS | TLS configuration options, including CA certificate, client certificate, and client key. | -| HMAC Signature | HMAC signature configuration options. | +| Option | Description | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| HTTP Method | Specifies the HTTP method to use: `POST` or `PUT`. | +| Basic Authentication Username | Username for HTTP Basic Authentication. | +| Basic Authentication Password | Password for HTTP Basic Authentication. | +| Authentication Header Scheme | Scheme for the `Authorization` Request Header. Default is `Bearer`. | +| Authentication Header Credentials | Credentials for the `Authorization` Request header. | +| Extra Headers | Additional HTTP headers to include in the request. You can also override the default `Content-Type: application/json` header to specify a different content type for the request payload. | +| Max Alerts | Maximum number of alerts to include in a notification. Any alerts exceeding this limit are ignored. `0` means no limit. | +| TLS | TLS configuration options, including CA certificate, client certificate, and client key. | +| HMAC Signature | HMAC signature configuration options. | {{< admonition type="note" >}} From 69f68cb73e087022fa1b89219bf96525193fa498 Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 20:33:38 +0000 Subject: [PATCH 022/146] Release: update changelog for 11.5.3+security-01 (#104340) * Update changelog * baldm0mma/ update changelog --------- Co-authored-by: github-actions[bot] Co-authored-by: jev forsberg --- CHANGELOG.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02cf2ea7dfc..4ec8623ba21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,26 @@ + + +# 11.5.3+security-01 (2025-04-22) + +### Features and enhancements + +- **Chore:** Bump Go to 1.23.7 [#101581](https://github.com/grafana/grafana/pull/101581), [@macabu](https://github.com/macabu) +- **Chore:** Bump Go to 1.23.7 (Enterprise) +- **Chore:** Update CVE-affected dependencies [#102709](https://github.com/grafana/grafana/pull/102709), [@grambbledook](https://github.com/grambbledook) + +### Bug fixes + +- **Alerting:** Fix token-based Slack image upload to work with channel names [#101078](https://github.com/grafana/grafana/pull/101078), [@JacobsonMT](https://github.com/JacobsonMT) +- **Auth:** Fix AzureAD config UI's ClientAuthentication dropdown [#100869](https://github.com/grafana/grafana/pull/100869), [@mgyongyosi](https://github.com/mgyongyosi) +- **Dashboard:** Fix the unintentional time range and variables updates on saving [#101671](https://github.com/grafana/grafana/pull/101671), [@harisrozajac](https://github.com/harisrozajac) +- **Dashboards:** Fix missing `v/e/i` keybindings to return back to dashboard [#102365](https://github.com/grafana/grafana/pull/102365), [@mdvictor](https://github.com/mdvictor) +- **InfluxDB:** Improve handling of template variables contained in regular expressions (InfluxQL) [#100977](https://github.com/grafana/grafana/pull/100977), [@aangelisc](https://github.com/aangelisc) +- **LDAP test:** Fix page crash [#102683](https://github.com/grafana/grafana/pull/102683), [@ashharrison90](https://github.com/ashharrison90) +- **Org redirection:** Fix linking between orgs [#102089](https://github.com/grafana/grafana/pull/102089), [@ashharrison90](https://github.com/ashharrison90) +- **Security:** Fix CVE-2025-3454 +- **Security:** Fix CVE-2025-2703 + + # 11.4.3+security-01 (2025-04-22) From d0154e8e778a4c19197eff04e7aef26719e27543 Mon Sep 17 00:00:00 2001 From: Pepe Cano <825430+ppcano@users.noreply.github.com> Date: Tue, 22 Apr 2025 22:45:48 +0200 Subject: [PATCH 023/146] docs(alerting): admonition for webhook features that are not GA in GC (#104331) * docs(alerting): admonition for webhook features that are not GA in GC * fix plural * minor copy change --- .../manage-contact-points/integrations/webhook-notifier.md | 6 ++++++ .../template-notifications/reference.md | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md index 726b9d9cef5..53e3025a527 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md @@ -254,6 +254,12 @@ The Alert object represents an alert included in the notification group, as prov ## Custom Payload +{{< admonition type="note" >}} + +Custom Payload is not yet [generally available](https://grafana.com/docs/release-life-cycle/#general-availability) in Grafana Cloud. + +{{< /admonition >}} + The `Custom Payload` option allows you to completely customize the webhook payload using templates. This gives you full control over the structure and content of the webhook request. | Option | Description | diff --git a/docs/sources/alerting/configure-notifications/template-notifications/reference.md b/docs/sources/alerting/configure-notifications/template-notifications/reference.md index 665e15857c9..d9d5cc75fa4 100644 --- a/docs/sources/alerting/configure-notifications/template-notifications/reference.md +++ b/docs/sources/alerting/configure-notifications/template-notifications/reference.md @@ -269,6 +269,12 @@ You can then use `tz` to change the timezone from UTC to local time, such as `Eu ## Namespaced Functions +{{< admonition type="note" >}} + +Namespaced Functions are not yet [generally available](https://grafana.com/docs/release-life-cycle/#general-availability) in Grafana Cloud. + +{{< /admonition >}} + In addition to the top-level functions, the following namespaced functions are also available: ### Collection Functions From 6abe6499c48182f26cb85ee963a63a85cba090ef Mon Sep 17 00:00:00 2001 From: Pepe Cano <825430+ppcano@users.noreply.github.com> Date: Tue, 22 Apr 2025 22:56:56 +0200 Subject: [PATCH 024/146] docs(alerting): clarify how Math expressions operate on multiple series (#104316) --- .../alert-rules/queries-conditions.md | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/docs/sources/alerting/fundamentals/alert-rules/queries-conditions.md b/docs/sources/alerting/fundamentals/alert-rules/queries-conditions.md index 25afe0227aa..d7c2f939ff4 100644 --- a/docs/sources/alerting/fundamentals/alert-rules/queries-conditions.md +++ b/docs/sources/alerting/fundamentals/alert-rules/queries-conditions.md @@ -32,6 +32,11 @@ refs: destination: /docs/grafana//panels-visualizations/query-transform-data/ - pattern: /docs/grafana-cloud/ destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/ + math-operation: + - pattern: /docs/grafana/ + destination: /docs/grafana//panels-visualizations/query-transform-data/expression-queries/#math + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/expression-queries/#math --- # Queries and conditions @@ -88,15 +93,23 @@ The following aggregations functions are included: `Min`, `Max`, `Mean`, `Mediam ### Math -Performs free-form math functions/operations on time series data and numbers. For instance, `$A + 1` or `$A * 100`. +Performs free-form math functions/operations on time series data and numbers. For example, `$A + 1` or `$A * 100`. -You can also use a Math expression to define the alert condition for numbers. For example: +If queries being compared have **multiple series in their results**, series from different queries are matched(joined) if they have the same labels. For example: + +- `$A` returns series `{host=web01} 30` and `{host=web02} 20` +- `$B` returns series `{host=web01} 10` and `{host=web02} 0` +- `$A + $B` returns `{host=web01} 40` and `{host=web02} 20`. + +In this case, only series with matching labels are joined, and the operation is calculated between them. + +For additional scenarios on how Math handles different data types, refer to the [Math documentation](ref:math-operation). + +You can also use a Math expression to define the **alert condition**. For example: - `$B > 70` should fire if the value of B (query or expression) is more than 70. - `$B < $C * 100` should fire if the value of B is less than the value of C multiplied by 100. -If queries being compared have multiple series in their results, series from different queries are matched if they have the same labels or one is a subset of the other. - ### Resample Realigns a time range to a new set of timestamps, this is useful when comparing time series data from different data sources where the timestamps would otherwise not align. From 6e4e2567783daa875ffe47b3653b2ecef3f4399c Mon Sep 17 00:00:00 2001 From: Kevin Minehart <5140827+kminehart@users.noreply.github.com> Date: Tue, 22 Apr 2025 16:03:19 -0500 Subject: [PATCH 025/146] Chore: update changelog for 11.6.0.1 (#104351) * update changelog for 11.6.0.1 * baldm0mma/ add cve --------- Co-authored-by: jev forsberg Co-authored-by: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com> --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ec8623ba21..1e49da3447c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ + + +# 11.6.0+security-01 (2025-04-22) + +### Bug fixes + +- **Security:** Fix CVE-2025-3454 +- **Security:** Fix CVE-2025-2703 +- **Security:** Fix CVE-2025-3260 + + # 11.5.3+security-01 (2025-04-22) From 092727e8f7f0aa674db105c898c42e01a4627b41 Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 16:00:46 -0600 Subject: [PATCH 026/146] Release: update changelog for 10.4.18 (#104355) * Update changelog * baldm0mma/ add cve --------- Co-authored-by: github-actions[bot] Co-authored-by: jev forsberg --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e49da3447c..855d02cb58f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ + + +# 10.4.18 (2025-04-22) + +### Features and enhancements + +- **Chore:** Bump golang-jwt/jwt/v4 and golang-jwt/jwt/v5 to address security issues [#102762](https://github.com/grafana/grafana/pull/102762), [@macabu](https://github.com/macabu) +- **Go:** Bump to 1.24.2 [#103531](https://github.com/grafana/grafana/pull/103531), [@Proximyst](https://github.com/Proximyst) +- **Go:** Bump to 1.24.2 (Enterprise) + +### Bug fixes + +- **Auth:** Fix SAML user IsExternallySynced not being set correctly (#98487) [#103177](https://github.com/grafana/grafana/pull/103177), [@volcanonoodle](https://github.com/volcanonoodle) +- **AuthN:** Refetch user on "ErrUserAlreadyExists" [#102981](https://github.com/grafana/grafana/pull/102981), [@kalleep](https://github.com/kalleep) +- **Security:** Fix CVE-2025-3454 + + # 11.6.0+security-01 (2025-04-22) From d03b938d2b67eeb3b4ef886f9ea59ff68e424d34 Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 22:23:50 +0000 Subject: [PATCH 027/146] Release: update changelog for 11.2.9 (#104357) * Update changelog * baldm0mma/ add cves --------- Co-authored-by: github-actions[bot] Co-authored-by: jev forsberg Co-authored-by: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com> --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 855d02cb58f..0014d46fec4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ + + +# 11.2.9 (2025-04-22) + +### Features and enhancements + +- **Chore:** Update libs with CVE in dependencies [#102712](https://github.com/grafana/grafana/pull/102712), [@grambbledook](https://github.com/grambbledook) +- **Go:** Bump to 1.24.2 [#103529](https://github.com/grafana/grafana/pull/103529), [@Proximyst](https://github.com/Proximyst) +- **Go:** Bump to 1.24.2 (Enterprise) + +### Bug fixes + +- **Auth:** Fix SAML user IsExternallySynced not being set correctly [#103102](https://github.com/grafana/grafana/pull/103102), [@volcanonoodle](https://github.com/volcanonoodle) +- **AuthN:** Refetch user on "ErrUserAlreadyExists" [#102982](https://github.com/grafana/grafana/pull/102982), [@kalleep](https://github.com/kalleep) +- **Security:** Fix CVE-2025-3454 +- **Security:** Fix CVE-2025-2703 + + # 10.4.18 (2025-04-22) From 028a3aaafa7ed514c7d99b5759d6b53b32fa1258 Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 17:39:52 -0600 Subject: [PATCH 028/146] Release: update changelog for 11.3.6 (#104358) * Update changelog * Update CHANGELOG.md --------- Co-authored-by: github-actions[bot] Co-authored-by: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com> --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0014d46fec4..23aeae4f688 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ + + +# 11.3.6 (2025-04-22) + +### Features and enhancements + +- **Chore:** Update libs with CVE in dependencies [#102710](https://github.com/grafana/grafana/pull/102710), [@grambbledook](https://github.com/grambbledook) +- **Go:** Bump to 1.24.2 [#103528](https://github.com/grafana/grafana/pull/103528), [@Proximyst](https://github.com/Proximyst) +- **Go:** Bump to 1.24.2 (Enterprise) + +### Bug fixes + +- **Auth:** Fix SAML user IsExternallySynced not being set correctly [#103101](https://github.com/grafana/grafana/pull/103101), [@volcanonoodle](https://github.com/volcanonoodle) +- **AuthN:** Refetch user on "ErrUserAlreadyExists" [#102983](https://github.com/grafana/grafana/pull/102983), [@kalleep](https://github.com/kalleep) +- **Security:** Fix CVE-2025-3454 +- **Security:** Fix CVE-2025-2703 + + # 11.2.9 (2025-04-22) From 410c5ebfb73e6a387a63b1ac3e561f9887f54087 Mon Sep 17 00:00:00 2001 From: "grafana-pr-automation[bot]" <140550294+grafana-pr-automation[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 01:36:28 +0100 Subject: [PATCH 029/146] I18n: Download translations from Crowdin (#104364) New Crowdin translations by GitHub Action Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- public/locales/cs-CZ/grafana.json | 326 +++++++++++++++++++++++++--- public/locales/de-DE/grafana.json | 314 ++++++++++++++++++++++++--- public/locales/es-ES/grafana.json | 314 ++++++++++++++++++++++++--- public/locales/fr-FR/grafana.json | 314 ++++++++++++++++++++++++--- public/locales/hu-HU/grafana.json | 314 ++++++++++++++++++++++++--- public/locales/id-ID/grafana.json | 308 +++++++++++++++++++++++--- public/locales/it-IT/grafana.json | 314 ++++++++++++++++++++++++--- public/locales/ja-JP/grafana.json | 308 +++++++++++++++++++++++--- public/locales/ko-KR/grafana.json | 308 +++++++++++++++++++++++--- public/locales/nl-NL/grafana.json | 314 ++++++++++++++++++++++++--- public/locales/pl-PL/grafana.json | 326 +++++++++++++++++++++++++--- public/locales/pt-BR/grafana.json | 314 ++++++++++++++++++++++++--- public/locales/pt-PT/grafana.json | 314 ++++++++++++++++++++++++--- public/locales/ru-RU/grafana.json | 326 +++++++++++++++++++++++++--- public/locales/sv-SE/grafana.json | 314 ++++++++++++++++++++++++--- public/locales/tr-TR/grafana.json | 314 ++++++++++++++++++++++++--- public/locales/zh-Hans/grafana.json | 308 +++++++++++++++++++++++--- public/locales/zh-Hant/grafana.json | 308 +++++++++++++++++++++++--- 18 files changed, 5190 insertions(+), 468 deletions(-) diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index 21311258107..5907fadf05d 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -1,6 +1,5 @@ { "_comment": "Kód představuje zdroj skutečností pro anglické fráze. Měly by být aktualizovány přímo v komponentách a tento soubor by měl obsahovat varianty množného čísla.", - "{{pageStart}} - {{pageEnd}} of {{numPages}}": "", "access-control": { "add-permission": { "built-in-aria-label": "", @@ -981,7 +980,8 @@ "expression-result": { "aria-label-nextpage": "", "aria-label-previouspage": "", - "no-data": "" + "no-data": "", + "page-counter": "" }, "federated-rule-warning": { "experimental": "", @@ -2024,6 +2024,10 @@ "title": "Pravidla výstrah" }, "rulerrule-loading-error": "Pravidlo se nepodařilo načíst", + "toggle": { + "go-back-to-old-look": "", + "try-out-the-new-look": "" + }, "unknown-rule-type": "" }, "rule-list-errors": { @@ -2539,6 +2543,18 @@ "configuration-required": "", "refer-documentation-configure-authentication": "" }, + "fields": { + "allowed-groups-description": "", + "allowed-groups-description-oauth": "", + "api-url-description": "", + "team-ids-description": "", + "team-ids-description-oauth": "", + "team-ids-github": "", + "team-ids-other": "", + "teams-url-description": "", + "teams-url-description-oauth": "", + "use-pkce-description": "" + }, "provider-card": { "text-badge-enabled": "", "text-badge-not-enabled": "" @@ -2552,6 +2568,8 @@ "label-enabled": "", "reset-configuration": "", "reset-configuration-description": "", + "save": "", + "saving": "", "title-more-actions": "", "title-reset": "", "tooltip-more-actions": "" @@ -2707,6 +2725,9 @@ "canvas": { "not-found-display": { "not-found": "" + }, + "text-display": { + "double-click-to-set": "" } }, "carousel": { @@ -2983,7 +3004,9 @@ }, "annotation-settings-list": { "aria-label-delete": "", + "built-in": "", "data-source": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3096,6 +3119,7 @@ } }, "confirm-plugin-dashboard-save-modal": { + "body-plugin-dashboard": "", "cancel": "", "overwrite": "", "title-plugin-dashboard": "" @@ -3118,6 +3142,7 @@ } }, "dashboard-validation": { + "body-dashboard-failed-schema-validation": "", "title-checking-dashboard-validity": "", "title-dashboard-failed-schema-validation": "", "title-error-checking-dashboard-validity": "" @@ -3252,6 +3277,7 @@ }, "gen-aihistory": { "aria-label-send-custom-feedback": "", + "footer-text": "", "placeholder-tell-ai-what-to-do-next": "" }, "get-field-override-categories": { @@ -3342,9 +3368,13 @@ "make-editable": "" }, "minimalistic-pagination": { + "page-count": "", "tooltip-next": "", "tooltip-previous": "" }, + "new-dashboard-with-ds": { + "not-found": "" + }, "new-panel": { "configure-button": "", "configure-button-menu": "", @@ -3409,9 +3439,12 @@ "go-back-to-queries": "" }, "public-dashboard-not-available": { + "does-not-exist": "", + "paused": "", "try-again-later": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, "row-options-button": { @@ -3473,6 +3506,8 @@ "description": "", "label-copy-tags": "", "label-folder": "", + "save": "", + "saving": "", "title": "" }, "save-dashboard-button": { @@ -3489,19 +3524,30 @@ "label-details": "" }, "save-dashboard-error-proxy": { - "title-conflict": "" + "body-name-exists": "", + "body-version-mismatch": "", + "title-name-exists": "", + "title-version-mismatch": "" }, "save-dashboard-form": { "cancel": "", "label-current-range-dashboard-default": "", "label-current-variable-values-dashboard-default": "", "no-changes-to-save": "", - "placeholder-describe-changes": "" + "placeholder-describe-changes": "", + "save": "", + "saving": "" }, "save-provisioned-dashboard-form": { "cancel": "", + "cannot-be-saved": "", "copy-json-to-clipboard": "", - "save-json-to-file": "" + "file-path": "", + "save-json-to-file": "", + "see-docs": "" + }, + "share-public-dashboard-loader": { + "loading-configuration": "" }, "solo-panel": { "loading-initializing-dashboard": "", @@ -3680,6 +3726,9 @@ "title-delete": "" }, "transformation-picker": { + "info": "", + "info-graph-not-suitable": "", + "info-switch-to-table": "", "placeholder-search-for-transformation": "", "read-more": "", "title-transformations": "" @@ -3704,6 +3753,9 @@ "discard": "", "title-unsaved-changes": "" }, + "untheme-dashboard-row": { + "dashboard-datasource": "" + }, "unthemed-dashboard-row": { "aria-label-delete-row": "", "learn-more": "" @@ -3715,7 +3767,10 @@ "tags-expected-strings": "očekávané pole řetězců tagů" }, "version-history-comparison": { - "label-view-json-diff": "" + "button-restore": "", + "label-view-json-diff": "", + "new-updated-by": "", + "old-updated-by": "" }, "version-history-table": { "aria-label-toggle-selection": "", @@ -3814,8 +3869,10 @@ "title-annotation-support-source": "" }, "annotation-settings-list": { + "built-in": "", "data-source": "", "delete-aria-label": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3876,6 +3933,8 @@ }, "data-source-variable-form": { "data-source-options": "", + "description-instance-name-filter": "", + "example-instance-name-filter": "", "selection-options": "" }, "description-label": { @@ -3936,7 +3995,15 @@ "title-plugin-dashboard": "", "title-someone-else-has-updated-this-dashboard": "", "would-still-dashboard": "" - } + }, + "save-and-overwrite": "" + }, + "library-viz-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_few": "", + "usage-count_many": "", + "usage-count_other": "" }, "name-already-exists-error": { "body-name-already-exists": "", @@ -3946,6 +4013,7 @@ "alert": { "title-errors-loading-rules": "" }, + "error-failed-to-load": "", "text-loading-rules": "", "title-dashboard-not-saved": "" }, @@ -3975,7 +4043,10 @@ "title-close": "" }, "provisioned-delete-modal": { + "cannot-be-deleted": "", + "file-path": "", "ok": "", + "see-docs": "", "title-cannot-delete-provisioned-dashboard": "" }, "provisioned-resource-delete-modal": { @@ -3988,13 +4059,21 @@ "query": "" }, "query-variable-editor-form": { + "description-examples": "", + "description-optional": "", "label-data-source": "", "query-options": "", "selection-options": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, + "save-button": { + "save": "", + "save-and-overwrite": "", + "saving": "" + }, "save-dashboard-as-form": { "aria-label-save-dashboard-description-field": "", "aria-label-save-dashboard-title-field": "", @@ -4047,17 +4126,23 @@ "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "affected-dashboards_one": "", + "affected-dashboards_few": "", + "affected-dashboards_many": "", + "affected-dashboards_other": "" }, "save-provisioned-dashboard-form": { "api-error": "Chyba při ukládání nástěnky", "api-success": "Změny na nástěnce uloženy", "cancel": "Zrušit", + "cannot-be-saved": "", "copy-json-message": "Pokud máte přímý přístup k cíli, zkopírujte JSON a vložte ho tam.", "copy-json-to-clipboard": "", "dashboard-comment-placeholder-describe-changes-optional": "Přidat poznámku k popisu změn (nepovinné)", "description-branch-name-in-git-hub": "Název větvě v GitHubu", "description-inside-repository": "Cesta k souboru uvnitř větve (.json nebo .yaml)", + "file-path": "", "label-branch": "Větev", "label-comment": "Komentář", "label-description": "Popis", @@ -4068,6 +4153,7 @@ "save": "Uložit", "save-json-to-file": "", "saving": "Ukládání…", + "see-docs": "", "title-required": "Název nástěnky je povinný", "title-same-as-folder": "Název nástěnky nemůže být stejný jako název složky", "title-this-repository-is-read-only": "Toto úložiště je jen pro čtení", @@ -4086,6 +4172,9 @@ "share-button": { "aria-label-sharedropdownmenu": "" }, + "solo-panel-page": { + "loading": "" + }, "text-box-variable-form": { "placeholder-default-value-if-any": "", "text-options": "" @@ -4145,10 +4234,18 @@ "preview-of-values": "", "show-more": "" }, + "version-history": { + "comparison": { + "button-restore": "" + } + }, "version-history-comparison": { - "label-view-json-diff": "" + "label-view-json-diff": "", + "new-version-updated": "", + "old-version-updated": "" }, "version-history-header": { + "compare-versions": "", "latest": "", "tooltip-reset-version": "" }, @@ -4324,6 +4421,7 @@ }, "button-row": { "delete": "", + "save-and-test": "", "test": "" }, "cloud-info-box": { @@ -4347,6 +4445,9 @@ "data-source-picker": { "aria-label-select-a-data-source": "" }, + "data-source-plugin-config-page": { + "page-not-found": "" + }, "data-source-plugin-state": { "plugin-state": "" }, @@ -4483,8 +4584,12 @@ } }, "explore": { + "accordian-logs": { + "footer": "" + }, "accordian-references": { - "references": "" + "references": "", + "view-linked-span": "" }, "add-to-dashboard": "Přidat na nástěnku", "basic-extensions": { @@ -4493,6 +4598,7 @@ "clear": "", "confirm-navigation-modal": { "cancel": "", + "new-tab": "", "open": "", "open-in-new-tab": "" }, @@ -4513,9 +4619,14 @@ "add-transformation": "", "aria-label-delete-transformation": "", "aria-label-edit-transformation": "", + "body-correlation-details": "", + "expression": "", "label-description": "", + "label-description-header": "", "label-label": "", - "title-correlation-details": "" + "title-correlation-details": "", + "tooltip-transformations": "", + "transformations": "" }, "correlation-transformation-add-modal": { "add-transformation": "", @@ -4541,10 +4652,13 @@ "title-unsaved-changes-to-correlation": "" }, "draggable-manager-demo": { + "click-and-drag-gray-divider": "", "click-horizontally-somewhere-colored-below": "", + "drag-value": "", "draggable-manager-demo": "", "dragging-a-divider": "", - "dragging-a-sub-region": "" + "dragging-a-sub-region": "", + "value": "" }, "drilldownInfo": { "action": "Přejít na Grafana Drilldown", @@ -4575,6 +4689,10 @@ "outline": "", "tooltip-content-outline": "" }, + "feature-toggle-page": { + "description-explore-disabled": "", + "title-explore-disabled": "" + }, "get-matches-metadata": { "matches": "" }, @@ -4585,10 +4703,15 @@ }, "live-logs": { "clear-logs": "", - "exit-live-mode": "" + "exit-live-mode": "", + "last-line-received": "", + "pause": "", + "resume": "" }, "live-tail-button": { + "live": "", "pause-the-live-stream": "", + "paused": "", "start-live-stream-your-logs": "", "stop-and-exit-the-live-stream": "" }, @@ -4617,12 +4740,14 @@ "show-original-line": "" }, "logs-sample-panel": { + "label": "", "logs-sample-is-loading": "", "no-logs-sample-data": "", "open-in-split-view-button": { "open-logs-in-split-view": "" }, - "title-failed-sample-query": "" + "title-failed-sample-query": "", + "tooltip": "" }, "logs-table-available-fields": { "title-label-percentage": "" @@ -4631,7 +4756,9 @@ "no-fields": "" }, "logs-table-multi-select": { - "fields": "" + "fields": "", + "reset": "", + "selected-fields": "" }, "logs-table-nav-field": { "aria-label-drag-and-drop-icon": "", @@ -4650,13 +4777,20 @@ "title-showing-partial-data": "", "title-unable-to-show-log-volume": "" }, + "logs-volumne-panel-list": { + "body-no-logs-volume-available": "" + }, "make-datasource-setup": { "aria-label-query": "" }, "next": "", "next-prev-result": { "aria-label-next": "", - "aria-label-prev": "" + "aria-label-prev": "", + "depth": "", + "depth-span-filter-matches": "", + "services": "", + "services-span-filter-matches": "" }, "no-data-source-call-to-action": { "cta-element": { @@ -4672,6 +4806,7 @@ "aria-label-go-queryless": "" }, "raw-list-container": { + "item-count": "", "label-expand-results": "" }, "raw-list-item": { @@ -4687,7 +4822,6 @@ "newest-first": "Seřadit od nejnovějších", "oldest-first": "Seřadit od nejstarších", "query-history": "Historie dotazů", - "query-library": "Knihovna dotazů", "settings": "Nastavení", "starred": "Označeno hvězdičkou" }, @@ -4803,6 +4937,9 @@ }, "show-critical-path-only": "", "show-matches-only": "", + "span-bar": { + "tooltip-critical-path": "" + }, "span-bar-section": { "description-link": "", "title-span-bar": "" @@ -4829,6 +4966,7 @@ "aria-label-select-service-name-operator": "", "aria-label-select-span-name": "", "aria-label-select-span-name-operator": "", + "label-collapse": "", "label-duration": "", "label-service-name": "", "label-span-name": "", @@ -4872,6 +5010,9 @@ "tooltip-expand": "", "tooltip-expand-all": "" }, + "timeline-header-row": { + "title": "" + }, "toolbar": { "add-to-extensions": "Přidat", "add-to-queryless-extensions": "Vyhnout se dotazům", @@ -4901,7 +5042,8 @@ "title-share-thoughts-about-tracing-grafana": "" }, "trace-page-header": { - "text-partial-trace": "" + "text-partial-trace": "", + "tooltip-url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "", @@ -4915,6 +5057,7 @@ "title-trace": "" }, "unconnected-node-graph-container": { + "count-warning": "", "title-node-graph": "" }, "unthemed-logs": { @@ -4968,11 +5111,20 @@ "classic-conditions": { "label-conditions": "" }, + "condition": { + "of": "", + "to": "", + "when": "" + }, "expression-query-editor": { "label-operation": "" }, "math": { - "available-math-functions": "" + "available-math-functions": "", + "run-math-operations": "", + "tooltip-footer": "", + "tooltip-title": "", + "tooltip-trigger": "" }, "reduce": { "label-function": "", @@ -5051,6 +5203,12 @@ }, "essentials": { "title-essentials": "" + }, + "progress-bar": { + "steps-status": "" + }, + "progress-status": { + "your-progress": "" } }, "grafana": { @@ -5442,8 +5600,14 @@ }, "inspector": { "inspect-data-tab": { + "loading": "", "no-data": "" }, + "inspect-error-tab": { + "error-status-message": "", + "error-status-no-message": "", + "error-trace-message": "" + }, "inspect-jsontab": { "apply": "", "support": "" @@ -5458,7 +5622,15 @@ }, "query-inspector": { "query-inspector": "", - "text-loading-query-inspector": "" + "text-loading-query-inspector": "", + "count-frames_one": "", + "count-frames_few": "", + "count-frames_many": "", + "count-frames_other": "", + "count-rows_one": "", + "count-rows_few": "", + "count-rows_many": "", + "count-rows_other": "" } }, "invites": { @@ -5470,6 +5642,11 @@ "name": "" }, "signup-invited-page": { + "complete-following": "", + "custom-has-invited-you": "", + "default-has-invited-you": "", + "greeting-custom": "", + "greeting-default": "", "label-email": "", "label-name": "", "label-password": "", @@ -5665,6 +5842,13 @@ "has-connected-dashboards": { "dashboard-name": "" }, + "library-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_few": "", + "usage-count_many": "", + "usage-count_other": "" + }, "library-panels-search": { "placeholder-search-by-name-or-description": "" }, @@ -5693,12 +5877,17 @@ "success": "Panel knihovny uložen" }, "save-library-panel-modal": { + "affected-dashboards": "", "cancel": "", "dashboard-name": "", "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "num-affected_one": "", + "num-affected_few": "", + "num-affected_many": "", + "num-affected_other": "" } }, "link": { @@ -5723,6 +5912,7 @@ "live": { "dashboard-changed-modal": { "continue-editing": "", + "description": "", "discard-local-changes": "", "title-dashboard-changed": "" }, @@ -5801,6 +5991,9 @@ "collapse": "", "expand": "" }, + "log-labels-list": { + "log-line": "" + }, "log-line-menu": { "copy-link": "Kopírovat odkaz na řádek protokolu", "copy-log": "Kopírovat řádek protokolu", @@ -5890,6 +6083,8 @@ "aria-label-no-details": "" }, "un-themed-log-details-row": { + "filter-out": "", + "filter-out-query": "", "title-copy-value-to-clipboard": "", "toggle-field-button": { "tooltip-field-instead-message": "", @@ -5897,6 +6092,10 @@ }, "tooltip-adhoc-statistics": "", "tooltip-hide-adhoc-statistics": "" + }, + "un-themed-log-label-stats": { + "field-log-stats": "", + "label-log-stats": "" } }, "manage-dashbaords": { @@ -5918,6 +6117,7 @@ "options": "" }, "import-dashboard-overview-un-connected": { + "importing-from": "", "published-by": "", "updated-on": "" }, @@ -5977,6 +6177,9 @@ "button": "Migrovat tuto instanci na cloud", "header": "Nechte správu vaší sady na Grafaně" }, + "dashboard-info": { + "dashboard": "" + }, "delete-migration-token-confirm": { "body": "Pokud už jste tento token použili se samostatně spravovanou instalací, tato instalace už nebude moct nahrávat obsah.", "confirm-button": "Odstranit token", @@ -5992,6 +6195,7 @@ "title": "Odpojit od cloudové sady" }, "folder-info": { + "folder": "", "unable-to-load-folder": "" }, "get-started": { @@ -6614,6 +6818,7 @@ "org": { "new-org-page": { "create": "", + "description": "", "label-organization-name": "", "placeholder-org-name": "" }, @@ -6622,6 +6827,9 @@ "label-organization-profile": "", "update-organization-name": "" }, + "select-org-page": { + "description": "" + }, "user-invite-form": { "back": "", "label-email-or-username": "", @@ -6629,7 +6837,11 @@ "label-send-invite-email": "", "placeholder-optional": "", "role": "", - "submit": "" + "submit": "", + "tooltip": "" + }, + "user-invite-page": { + "sub-title": "" } }, "org-picker": { @@ -6672,7 +6884,9 @@ "title-not-found": "" }, "panel-renderer": { + "failed-to-load-plugin": "", "loading-plugin-panel": "", + "no-panel-component": "", "no-panel-data": "" }, "panel-type-card": { @@ -6699,7 +6913,14 @@ "select-placeholder": "" }, "playlist": { + "playlist-table-rows": { + "aria-label-playlist-item": "", + "multiple-dashboards-found": "", + "no-dashboards-found": "", + "not-found": "" + }, "start-modal": { + "button-start": "", "description-customize-dashboard-elements-visibility": "", "description-panel-heights-adjusted-screen": "", "label-autofit": "", @@ -6759,6 +6980,11 @@ "message": "Nebyly nalezeny žádné playlisty" } }, + "plugin": { + "plugin-details-deprecated-warning": { + "body-deprecated": "" + } + }, "plugins": { "app-root-page": { "access-denied": { @@ -6862,6 +7088,12 @@ "add-new-data-source": "", "title-button-disabled": "" }, + "install-controls": { + "install": "", + "installing": "", + "update": "", + "updating": "" + }, "install-controls-button": { "title-uninstall-modal": "" }, @@ -6880,6 +7112,7 @@ "label-severity": "" }, "not-found-plugin": { + "body-plugin-not-found": "", "title-plugin-not-found": "" }, "plugin-actions": { @@ -6894,12 +7127,17 @@ "tooltip-plugin-deprecated-longer-receives-updates": "" }, "plugin-details-body": { + "needs-service-account": "", "page-not-found": "" }, "plugin-details-deprecated-warning": { "title-deprecated": "" }, + "plugin-details-header-dependencies": { + "grafana-dependency": "" + }, "plugin-details-signature": { + "body-invalid-plugin-signature": "", "read-more-about-plugins-signing": "", "title-invalid-plugin-signature": "" }, @@ -6930,6 +7168,8 @@ "update-available": "" }, "plugin-usage": { + "body-missing-feature-toggle-panel-title-search": "", + "num-usages": "", "title-missing-feature-toggle-panel-title-search": "", "title-not-used-yet": "" }, @@ -6946,7 +7186,9 @@ }, "version-list": { "grafana-dependency": "", + "installed-version": "", "last-updated": "", + "latest-compatible-version": "", "no-version-history-was-found": "", "version": "" } @@ -6969,6 +7211,12 @@ }, "change-theme": "Změnit motiv", "enable-kiosk-mode": "", + "feature-toggle-page": { + "profile-not-enabled": "" + }, + "feature-toggles-age": { + "enable-in-config": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -7115,6 +7363,8 @@ "repository-config-exists-configuration": "" }, "file-status-page": { + "save": "", + "saving": "", "title-error-loading-file": "" }, "files-view": { @@ -7145,7 +7395,6 @@ }, "getting-started": { "alert-temporary-outage": "", - "engaging-graphic": "", "modal-description-public-access": "", "modal-description-required-features": "", "modal-title-set-up-public-access": "", @@ -7241,13 +7490,16 @@ "checked": "", "finished": "", "health": "", + "healthy": "", "job-id": "", "last-ref": "", "messages": "", + "not-available": "", "pull-status": "", "resources": "", "started": "", "status": "", + "unhealthy": "", "view-folder": "", "webhook": "", "webhook-events": "", @@ -7540,31 +7792,41 @@ "hidden": "", "query-name-div-title-edit-query-name": "" }, + "query-error-alert": { + "trace-id": "" + }, "query-group": { "add-query": "", "expression": "", "title-data-source-help": "" }, "query-group-options-editor": { + "collapsed-interval": "", + "collapsed-max-data-points": "", "hide-time-info": "", "Query options-title-query-options": "", "relative-time": "", + "relative-time-tooltip": "", "render-cache-timeout-option": { "cache-timeout": "" }, "render-interval-option": { "interval": "", + "interval-tooltip": "", "min-interval": "", + "min-interval-tooltip": "", "time-range-max-data-points": "" }, "render-max-data-points-option": { "max-data-points": "", + "max-data-points-tooltip": "", "width-of-panel": "" }, "render-query-caching-ttloption": { "cache-ttl": "" }, - "time-shift": "" + "time-shift": "", + "time-shift-tooltip": "" }, "query-group-top-section": { "data-source": "", @@ -7752,6 +8014,9 @@ } }, "search": { + "action-row": { + "panel-type": "" + }, "actions": { "include-panels": "Zahrnout panely", "remove-datasource-filter": "Zdroj dat: {{datasource}}", @@ -7875,6 +8140,9 @@ "label-roles": "", "used-by": "" }, + "service-account-profile-row": { + "edit": "" + }, "service-account-role-row": { "aria-label-role": "" }, @@ -8399,6 +8667,7 @@ "generate-enum-values-from-data": "" }, "enum-mapping-row": { + "click-to-edit": "", "remove-enum-row-aria-label-delete-enum-row": "", "remove-enum-row-tooltip-delete": "" }, @@ -8453,10 +8722,13 @@ "label-format": "", "label-set-timezone": "", "label-time-field": "", + "tooltip-format": "", "tooltip-timezone-manually": "" }, "get-tooltips": { - "json-value": "" + "description": "", + "json-value": "", + "valid-paths": "" }, "group-by-field-configuration": { "placeholder-ignored": "", @@ -8673,6 +8945,7 @@ "message": "Nebyli nalezeni žádní uživatelé" }, "token-revoked-modal": { + "auto-revoked": "", "resume-message": "", "sign-in": "", "title-you-have-been-automatically-signed-out": "" @@ -8767,7 +9040,8 @@ "general": "", "placeholder-descriptive-text": "", "placeholder-label-name": "", - "placeholder-variable-name": "" + "placeholder-variable-name": "", + "run-query": "" }, "variable-editor-list": { "definition": "", diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index 3c1c24f8314..c4ec43a2333 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -1,6 +1,5 @@ { "_comment": "Der Code ist die Source of Truth für englischsprachige Sätze. Sie sollten direkt in den Komponenten aktualisiert werden. Zusätzliche Sätze sollten in dieser Datei spezifiziert werden.", - "{{pageStart}} - {{pageEnd}} of {{numPages}}": "", "access-control": { "add-permission": { "built-in-aria-label": "", @@ -969,7 +968,8 @@ "expression-result": { "aria-label-nextpage": "", "aria-label-previouspage": "", - "no-data": "" + "no-data": "", + "page-counter": "" }, "federated-rule-warning": { "experimental": "", @@ -2004,6 +2004,10 @@ "title": "" }, "rulerrule-loading-error": "Die Regel konnte nicht geladen werden", + "toggle": { + "go-back-to-old-look": "", + "try-out-the-new-look": "" + }, "unknown-rule-type": "" }, "rule-list-errors": { @@ -2513,6 +2517,18 @@ "configuration-required": "", "refer-documentation-configure-authentication": "" }, + "fields": { + "allowed-groups-description": "", + "allowed-groups-description-oauth": "", + "api-url-description": "", + "team-ids-description": "", + "team-ids-description-oauth": "", + "team-ids-github": "", + "team-ids-other": "", + "teams-url-description": "", + "teams-url-description-oauth": "", + "use-pkce-description": "" + }, "provider-card": { "text-badge-enabled": "", "text-badge-not-enabled": "" @@ -2526,6 +2542,8 @@ "label-enabled": "", "reset-configuration": "", "reset-configuration-description": "", + "save": "", + "saving": "", "title-more-actions": "", "title-reset": "", "tooltip-more-actions": "" @@ -2671,6 +2689,9 @@ "canvas": { "not-found-display": { "not-found": "" + }, + "text-display": { + "double-click-to-set": "" } }, "carousel": { @@ -2947,7 +2968,9 @@ }, "annotation-settings-list": { "aria-label-delete": "", + "built-in": "", "data-source": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3060,6 +3083,7 @@ } }, "confirm-plugin-dashboard-save-modal": { + "body-plugin-dashboard": "", "cancel": "", "overwrite": "", "title-plugin-dashboard": "" @@ -3082,6 +3106,7 @@ } }, "dashboard-validation": { + "body-dashboard-failed-schema-validation": "", "title-checking-dashboard-validity": "", "title-dashboard-failed-schema-validation": "", "title-error-checking-dashboard-validity": "" @@ -3216,6 +3241,7 @@ }, "gen-aihistory": { "aria-label-send-custom-feedback": "", + "footer-text": "", "placeholder-tell-ai-what-to-do-next": "" }, "get-field-override-categories": { @@ -3306,9 +3332,13 @@ "make-editable": "" }, "minimalistic-pagination": { + "page-count": "", "tooltip-next": "", "tooltip-previous": "" }, + "new-dashboard-with-ds": { + "not-found": "" + }, "new-panel": { "configure-button": "", "configure-button-menu": "", @@ -3371,9 +3401,12 @@ "go-back-to-queries": "" }, "public-dashboard-not-available": { + "does-not-exist": "", + "paused": "", "try-again-later": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, "row-options-button": { @@ -3435,6 +3468,8 @@ "description": "", "label-copy-tags": "", "label-folder": "", + "save": "", + "saving": "", "title": "" }, "save-dashboard-button": { @@ -3451,19 +3486,30 @@ "label-details": "" }, "save-dashboard-error-proxy": { - "title-conflict": "" + "body-name-exists": "", + "body-version-mismatch": "", + "title-name-exists": "", + "title-version-mismatch": "" }, "save-dashboard-form": { "cancel": "", "label-current-range-dashboard-default": "", "label-current-variable-values-dashboard-default": "", "no-changes-to-save": "", - "placeholder-describe-changes": "" + "placeholder-describe-changes": "", + "save": "", + "saving": "" }, "save-provisioned-dashboard-form": { "cancel": "", + "cannot-be-saved": "", "copy-json-to-clipboard": "", - "save-json-to-file": "" + "file-path": "", + "save-json-to-file": "", + "see-docs": "" + }, + "share-public-dashboard-loader": { + "loading-configuration": "" }, "solo-panel": { "loading-initializing-dashboard": "", @@ -3642,6 +3688,9 @@ "title-delete": "" }, "transformation-picker": { + "info": "", + "info-graph-not-suitable": "", + "info-switch-to-table": "", "placeholder-search-for-transformation": "", "read-more": "", "title-transformations": "" @@ -3666,6 +3715,9 @@ "discard": "", "title-unsaved-changes": "" }, + "untheme-dashboard-row": { + "dashboard-datasource": "" + }, "unthemed-dashboard-row": { "aria-label-delete-row": "", "learn-more": "" @@ -3677,7 +3729,10 @@ "tags-expected-strings": "String-Array der jeweiligen Tags" }, "version-history-comparison": { - "label-view-json-diff": "" + "button-restore": "", + "label-view-json-diff": "", + "new-updated-by": "", + "old-updated-by": "" }, "version-history-table": { "aria-label-toggle-selection": "", @@ -3776,8 +3831,10 @@ "title-annotation-support-source": "" }, "annotation-settings-list": { + "built-in": "", "data-source": "", "delete-aria-label": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3838,6 +3895,8 @@ }, "data-source-variable-form": { "data-source-options": "", + "description-instance-name-filter": "", + "example-instance-name-filter": "", "selection-options": "" }, "description-label": { @@ -3898,7 +3957,13 @@ "title-plugin-dashboard": "", "title-someone-else-has-updated-this-dashboard": "", "would-still-dashboard": "" - } + }, + "save-and-overwrite": "" + }, + "library-viz-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_other": "" }, "name-already-exists-error": { "body-name-already-exists": "", @@ -3908,6 +3973,7 @@ "alert": { "title-errors-loading-rules": "" }, + "error-failed-to-load": "", "text-loading-rules": "", "title-dashboard-not-saved": "" }, @@ -3937,7 +4003,10 @@ "title-close": "" }, "provisioned-delete-modal": { + "cannot-be-deleted": "", + "file-path": "", "ok": "", + "see-docs": "", "title-cannot-delete-provisioned-dashboard": "" }, "provisioned-resource-delete-modal": { @@ -3950,13 +4019,21 @@ "query": "" }, "query-variable-editor-form": { + "description-examples": "", + "description-optional": "", "label-data-source": "", "query-options": "", "selection-options": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, + "save-button": { + "save": "", + "save-and-overwrite": "", + "saving": "" + }, "save-dashboard-as-form": { "aria-label-save-dashboard-description-field": "", "aria-label-save-dashboard-title-field": "", @@ -4009,17 +4086,21 @@ "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "affected-dashboards_one": "", + "affected-dashboards_other": "" }, "save-provisioned-dashboard-form": { "api-error": "Fehler beim Speichern des Dashboards", "api-success": "Dashboard-Änderungen gespeichert", "cancel": "", + "cannot-be-saved": "", "copy-json-message": "Wenn Sie direkten Zugriff auf das Ziel haben, kopieren Sie den JSON und fügen Sie ihn dort ein.", "copy-json-to-clipboard": "", "dashboard-comment-placeholder-describe-changes-optional": "Fügen Sie eine Notiz hinzu, um Ihre Änderungen zu beschreiben (optional)", "description-branch-name-in-git-hub": "Branch-Name in GitHub", "description-inside-repository": "Dateipfad innerhalb des Repositorys (.json oder .yaml)", + "file-path": "", "label-branch": "Branch", "label-comment": "Kommentar", "label-description": "", @@ -4030,6 +4111,7 @@ "save": "", "save-json-to-file": "", "saving": "Wird gespeichert …", + "see-docs": "", "title-required": "Dashboard-Titel ist erforderlich", "title-same-as-folder": "Der Dashboard-Name darf nicht mit dem Ordnernamen identisch sein", "title-this-repository-is-read-only": "Dieses Repository ist schreibgeschützt", @@ -4048,6 +4130,9 @@ "share-button": { "aria-label-sharedropdownmenu": "" }, + "solo-panel-page": { + "loading": "" + }, "text-box-variable-form": { "placeholder-default-value-if-any": "", "text-options": "" @@ -4107,10 +4192,18 @@ "preview-of-values": "", "show-more": "" }, + "version-history": { + "comparison": { + "button-restore": "" + } + }, "version-history-comparison": { - "label-view-json-diff": "" + "label-view-json-diff": "", + "new-version-updated": "", + "old-version-updated": "" }, "version-history-header": { + "compare-versions": "", "latest": "", "tooltip-reset-version": "" }, @@ -4286,6 +4379,7 @@ }, "button-row": { "delete": "", + "save-and-test": "", "test": "" }, "cloud-info-box": { @@ -4309,6 +4403,9 @@ "data-source-picker": { "aria-label-select-a-data-source": "" }, + "data-source-plugin-config-page": { + "page-not-found": "" + }, "data-source-plugin-state": { "plugin-state": "" }, @@ -4445,8 +4542,12 @@ } }, "explore": { + "accordian-logs": { + "footer": "" + }, "accordian-references": { - "references": "" + "references": "", + "view-linked-span": "" }, "add-to-dashboard": "Zum Dashboard hinzufügen", "basic-extensions": { @@ -4455,6 +4556,7 @@ "clear": "", "confirm-navigation-modal": { "cancel": "", + "new-tab": "", "open": "", "open-in-new-tab": "" }, @@ -4475,9 +4577,14 @@ "add-transformation": "", "aria-label-delete-transformation": "", "aria-label-edit-transformation": "", + "body-correlation-details": "", + "expression": "", "label-description": "", + "label-description-header": "", "label-label": "", - "title-correlation-details": "" + "title-correlation-details": "", + "tooltip-transformations": "", + "transformations": "" }, "correlation-transformation-add-modal": { "add-transformation": "", @@ -4503,10 +4610,13 @@ "title-unsaved-changes-to-correlation": "" }, "draggable-manager-demo": { + "click-and-drag-gray-divider": "", "click-horizontally-somewhere-colored-below": "", + "drag-value": "", "draggable-manager-demo": "", "dragging-a-divider": "", - "dragging-a-sub-region": "" + "dragging-a-sub-region": "", + "value": "" }, "drilldownInfo": { "action": "Zu Grafana Drilldown", @@ -4537,6 +4647,10 @@ "outline": "", "tooltip-content-outline": "" }, + "feature-toggle-page": { + "description-explore-disabled": "", + "title-explore-disabled": "" + }, "get-matches-metadata": { "matches": "" }, @@ -4547,10 +4661,15 @@ }, "live-logs": { "clear-logs": "", - "exit-live-mode": "" + "exit-live-mode": "", + "last-line-received": "", + "pause": "", + "resume": "" }, "live-tail-button": { + "live": "", "pause-the-live-stream": "", + "paused": "", "start-live-stream-your-logs": "", "stop-and-exit-the-live-stream": "" }, @@ -4579,12 +4698,14 @@ "show-original-line": "" }, "logs-sample-panel": { + "label": "", "logs-sample-is-loading": "", "no-logs-sample-data": "", "open-in-split-view-button": { "open-logs-in-split-view": "" }, - "title-failed-sample-query": "" + "title-failed-sample-query": "", + "tooltip": "" }, "logs-table-available-fields": { "title-label-percentage": "" @@ -4593,7 +4714,9 @@ "no-fields": "" }, "logs-table-multi-select": { - "fields": "" + "fields": "", + "reset": "", + "selected-fields": "" }, "logs-table-nav-field": { "aria-label-drag-and-drop-icon": "", @@ -4612,13 +4735,20 @@ "title-showing-partial-data": "", "title-unable-to-show-log-volume": "" }, + "logs-volumne-panel-list": { + "body-no-logs-volume-available": "" + }, "make-datasource-setup": { "aria-label-query": "" }, "next": "", "next-prev-result": { "aria-label-next": "", - "aria-label-prev": "" + "aria-label-prev": "", + "depth": "", + "depth-span-filter-matches": "", + "services": "", + "services-span-filter-matches": "" }, "no-data-source-call-to-action": { "cta-element": { @@ -4634,6 +4764,7 @@ "aria-label-go-queryless": "" }, "raw-list-container": { + "item-count": "", "label-expand-results": "" }, "raw-list-item": { @@ -4649,7 +4780,6 @@ "newest-first": "Neueste zuerst", "oldest-first": "Älteste zuerst", "query-history": "Abfrageverlauf", - "query-library": "Abfragebibliothek", "settings": "Einstellungen", "starred": "Hervorgehoben" }, @@ -4765,6 +4895,9 @@ }, "show-critical-path-only": "", "show-matches-only": "", + "span-bar": { + "tooltip-critical-path": "" + }, "span-bar-section": { "description-link": "", "title-span-bar": "" @@ -4791,6 +4924,7 @@ "aria-label-select-service-name-operator": "", "aria-label-select-span-name": "", "aria-label-select-span-name-operator": "", + "label-collapse": "", "label-duration": "", "label-service-name": "", "label-span-name": "", @@ -4834,6 +4968,9 @@ "tooltip-expand": "", "tooltip-expand-all": "" }, + "timeline-header-row": { + "title": "" + }, "toolbar": { "add-to-extensions": "", "add-to-queryless-extensions": "Abfrage aufheben", @@ -4863,7 +5000,8 @@ "title-share-thoughts-about-tracing-grafana": "" }, "trace-page-header": { - "text-partial-trace": "" + "text-partial-trace": "", + "tooltip-url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "", @@ -4877,6 +5015,7 @@ "title-trace": "" }, "unconnected-node-graph-container": { + "count-warning": "", "title-node-graph": "" }, "unthemed-logs": { @@ -4930,11 +5069,20 @@ "classic-conditions": { "label-conditions": "" }, + "condition": { + "of": "", + "to": "", + "when": "" + }, "expression-query-editor": { "label-operation": "" }, "math": { - "available-math-functions": "" + "available-math-functions": "", + "run-math-operations": "", + "tooltip-footer": "", + "tooltip-title": "", + "tooltip-trigger": "" }, "reduce": { "label-function": "", @@ -5013,6 +5161,12 @@ }, "essentials": { "title-essentials": "" + }, + "progress-bar": { + "steps-status": "" + }, + "progress-status": { + "your-progress": "" } }, "grafana": { @@ -5404,8 +5558,14 @@ }, "inspector": { "inspect-data-tab": { + "loading": "", "no-data": "" }, + "inspect-error-tab": { + "error-status-message": "", + "error-status-no-message": "", + "error-trace-message": "" + }, "inspect-jsontab": { "apply": "", "support": "" @@ -5420,7 +5580,11 @@ }, "query-inspector": { "query-inspector": "", - "text-loading-query-inspector": "" + "text-loading-query-inspector": "", + "count-frames_one": "", + "count-frames_other": "", + "count-rows_one": "", + "count-rows_other": "" } }, "invites": { @@ -5432,6 +5596,11 @@ "name": "" }, "signup-invited-page": { + "complete-following": "", + "custom-has-invited-you": "", + "default-has-invited-you": "", + "greeting-custom": "", + "greeting-default": "", "label-email": "", "label-name": "", "label-password": "", @@ -5627,6 +5796,11 @@ "has-connected-dashboards": { "dashboard-name": "" }, + "library-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_other": "" + }, "library-panels-search": { "placeholder-search-by-name-or-description": "" }, @@ -5653,12 +5827,15 @@ "success": "Bibliotheks-Panel wurde gespeichert" }, "save-library-panel-modal": { + "affected-dashboards": "", "cancel": "", "dashboard-name": "", "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "num-affected_one": "", + "num-affected_other": "" } }, "link": { @@ -5683,6 +5860,7 @@ "live": { "dashboard-changed-modal": { "continue-editing": "", + "description": "", "discard-local-changes": "", "title-dashboard-changed": "" }, @@ -5761,6 +5939,9 @@ "collapse": "", "expand": "" }, + "log-labels-list": { + "log-line": "" + }, "log-line-menu": { "copy-link": "Link zur Protokollzeile kopieren", "copy-log": "Protokollzeile kopieren", @@ -5850,6 +6031,8 @@ "aria-label-no-details": "" }, "un-themed-log-details-row": { + "filter-out": "", + "filter-out-query": "", "title-copy-value-to-clipboard": "", "toggle-field-button": { "tooltip-field-instead-message": "", @@ -5857,6 +6040,10 @@ }, "tooltip-adhoc-statistics": "", "tooltip-hide-adhoc-statistics": "" + }, + "un-themed-log-label-stats": { + "field-log-stats": "", + "label-log-stats": "" } }, "manage-dashbaords": { @@ -5878,6 +6065,7 @@ "options": "" }, "import-dashboard-overview-un-connected": { + "importing-from": "", "published-by": "", "updated-on": "" }, @@ -5937,6 +6125,9 @@ "button": "Diese Instanz in die Cloud migrieren", "header": "Lassen Sie uns Ihren Grafana Stack verwalten" }, + "dashboard-info": { + "dashboard": "" + }, "delete-migration-token-confirm": { "body": "", "confirm-button": "Token löschen", @@ -5952,6 +6143,7 @@ "title": "Vom Cloud Stack trennen" }, "folder-info": { + "folder": "", "unable-to-load-folder": "" }, "get-started": { @@ -6574,6 +6766,7 @@ "org": { "new-org-page": { "create": "", + "description": "", "label-organization-name": "", "placeholder-org-name": "" }, @@ -6582,6 +6775,9 @@ "label-organization-profile": "", "update-organization-name": "" }, + "select-org-page": { + "description": "" + }, "user-invite-form": { "back": "", "label-email-or-username": "", @@ -6589,7 +6785,11 @@ "label-send-invite-email": "", "placeholder-optional": "", "role": "", - "submit": "" + "submit": "", + "tooltip": "" + }, + "user-invite-page": { + "sub-title": "" } }, "org-picker": { @@ -6632,7 +6832,9 @@ "title-not-found": "" }, "panel-renderer": { + "failed-to-load-plugin": "", "loading-plugin-panel": "", + "no-panel-component": "", "no-panel-data": "" }, "panel-type-card": { @@ -6659,7 +6861,14 @@ "select-placeholder": "" }, "playlist": { + "playlist-table-rows": { + "aria-label-playlist-item": "", + "multiple-dashboards-found": "", + "no-dashboards-found": "", + "not-found": "" + }, "start-modal": { + "button-start": "", "description-customize-dashboard-elements-visibility": "", "description-panel-heights-adjusted-screen": "", "label-autofit": "", @@ -6719,6 +6928,11 @@ "message": "Keine Wiedergabelisten gefunden" } }, + "plugin": { + "plugin-details-deprecated-warning": { + "body-deprecated": "" + } + }, "plugins": { "app-root-page": { "access-denied": { @@ -6822,6 +7036,12 @@ "add-new-data-source": "", "title-button-disabled": "" }, + "install-controls": { + "install": "", + "installing": "", + "update": "", + "updating": "" + }, "install-controls-button": { "title-uninstall-modal": "" }, @@ -6840,6 +7060,7 @@ "label-severity": "" }, "not-found-plugin": { + "body-plugin-not-found": "", "title-plugin-not-found": "" }, "plugin-actions": { @@ -6854,12 +7075,17 @@ "tooltip-plugin-deprecated-longer-receives-updates": "" }, "plugin-details-body": { + "needs-service-account": "", "page-not-found": "" }, "plugin-details-deprecated-warning": { "title-deprecated": "" }, + "plugin-details-header-dependencies": { + "grafana-dependency": "" + }, "plugin-details-signature": { + "body-invalid-plugin-signature": "", "read-more-about-plugins-signing": "", "title-invalid-plugin-signature": "" }, @@ -6890,6 +7116,8 @@ "update-available": "" }, "plugin-usage": { + "body-missing-feature-toggle-panel-title-search": "", + "num-usages": "", "title-missing-feature-toggle-panel-title-search": "", "title-not-used-yet": "" }, @@ -6906,7 +7134,9 @@ }, "version-list": { "grafana-dependency": "", + "installed-version": "", "last-updated": "", + "latest-compatible-version": "", "no-version-history-was-found": "", "version": "" } @@ -6929,6 +7159,12 @@ }, "change-theme": "", "enable-kiosk-mode": "", + "feature-toggle-page": { + "profile-not-enabled": "" + }, + "feature-toggles-age": { + "enable-in-config": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -7073,6 +7309,8 @@ "repository-config-exists-configuration": "" }, "file-status-page": { + "save": "", + "saving": "", "title-error-loading-file": "" }, "files-view": { @@ -7103,7 +7341,6 @@ }, "getting-started": { "alert-temporary-outage": "", - "engaging-graphic": "", "modal-description-public-access": "", "modal-description-required-features": "", "modal-title-set-up-public-access": "", @@ -7199,13 +7436,16 @@ "checked": "", "finished": "", "health": "", + "healthy": "", "job-id": "", "last-ref": "", "messages": "", + "not-available": "", "pull-status": "", "resources": "", "started": "", "status": "", + "unhealthy": "", "view-folder": "", "webhook": "", "webhook-events": "", @@ -7496,31 +7736,41 @@ "hidden": "", "query-name-div-title-edit-query-name": "" }, + "query-error-alert": { + "trace-id": "" + }, "query-group": { "add-query": "", "expression": "", "title-data-source-help": "" }, "query-group-options-editor": { + "collapsed-interval": "", + "collapsed-max-data-points": "", "hide-time-info": "", "Query options-title-query-options": "", "relative-time": "", + "relative-time-tooltip": "", "render-cache-timeout-option": { "cache-timeout": "" }, "render-interval-option": { "interval": "", + "interval-tooltip": "", "min-interval": "", + "min-interval-tooltip": "", "time-range-max-data-points": "" }, "render-max-data-points-option": { "max-data-points": "", + "max-data-points-tooltip": "", "width-of-panel": "" }, "render-query-caching-ttloption": { "cache-ttl": "" }, - "time-shift": "" + "time-shift": "", + "time-shift-tooltip": "" }, "query-group-top-section": { "data-source": "", @@ -7702,6 +7952,9 @@ } }, "search": { + "action-row": { + "panel-type": "" + }, "actions": { "include-panels": "Panels einschließen", "remove-datasource-filter": "Datenquelle: {{datasource}}", @@ -7825,6 +8078,9 @@ "label-roles": "", "used-by": "" }, + "service-account-profile-row": { + "edit": "" + }, "service-account-role-row": { "aria-label-role": "" }, @@ -8349,6 +8605,7 @@ "generate-enum-values-from-data": "" }, "enum-mapping-row": { + "click-to-edit": "", "remove-enum-row-aria-label-delete-enum-row": "", "remove-enum-row-tooltip-delete": "" }, @@ -8403,10 +8660,13 @@ "label-format": "", "label-set-timezone": "", "label-time-field": "", + "tooltip-format": "", "tooltip-timezone-manually": "" }, "get-tooltips": { - "json-value": "" + "description": "", + "json-value": "", + "valid-paths": "" }, "group-by-field-configuration": { "placeholder-ignored": "", @@ -8623,6 +8883,7 @@ "message": "Keine Benutzer gefunden" }, "token-revoked-modal": { + "auto-revoked": "", "resume-message": "", "sign-in": "", "title-you-have-been-automatically-signed-out": "" @@ -8717,7 +8978,8 @@ "general": "", "placeholder-descriptive-text": "", "placeholder-label-name": "", - "placeholder-variable-name": "" + "placeholder-variable-name": "", + "run-query": "" }, "variable-editor-list": { "definition": "", diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index 9e9b345d975..00d8392d91b 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -1,6 +1,5 @@ { "_comment": "El código es la fuente de verdad para las frases en inglés. Deben actualizarse en los componentes directamente y en los plurales adicionales especificados en este archivo.", - "{{pageStart}} - {{pageEnd}} of {{numPages}}": "", "access-control": { "add-permission": { "built-in-aria-label": "", @@ -969,7 +968,8 @@ "expression-result": { "aria-label-nextpage": "", "aria-label-previouspage": "", - "no-data": "" + "no-data": "", + "page-counter": "" }, "federated-rule-warning": { "experimental": "", @@ -2004,6 +2004,10 @@ "title": "" }, "rulerrule-loading-error": "Error al cargar la regla", + "toggle": { + "go-back-to-old-look": "", + "try-out-the-new-look": "" + }, "unknown-rule-type": "" }, "rule-list-errors": { @@ -2513,6 +2517,18 @@ "configuration-required": "", "refer-documentation-configure-authentication": "" }, + "fields": { + "allowed-groups-description": "", + "allowed-groups-description-oauth": "", + "api-url-description": "", + "team-ids-description": "", + "team-ids-description-oauth": "", + "team-ids-github": "", + "team-ids-other": "", + "teams-url-description": "", + "teams-url-description-oauth": "", + "use-pkce-description": "" + }, "provider-card": { "text-badge-enabled": "", "text-badge-not-enabled": "" @@ -2526,6 +2542,8 @@ "label-enabled": "", "reset-configuration": "", "reset-configuration-description": "", + "save": "", + "saving": "", "title-more-actions": "", "title-reset": "", "tooltip-more-actions": "" @@ -2671,6 +2689,9 @@ "canvas": { "not-found-display": { "not-found": "" + }, + "text-display": { + "double-click-to-set": "" } }, "carousel": { @@ -2947,7 +2968,9 @@ }, "annotation-settings-list": { "aria-label-delete": "", + "built-in": "", "data-source": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3060,6 +3083,7 @@ } }, "confirm-plugin-dashboard-save-modal": { + "body-plugin-dashboard": "", "cancel": "", "overwrite": "", "title-plugin-dashboard": "" @@ -3082,6 +3106,7 @@ } }, "dashboard-validation": { + "body-dashboard-failed-schema-validation": "", "title-checking-dashboard-validity": "", "title-dashboard-failed-schema-validation": "", "title-error-checking-dashboard-validity": "" @@ -3216,6 +3241,7 @@ }, "gen-aihistory": { "aria-label-send-custom-feedback": "", + "footer-text": "", "placeholder-tell-ai-what-to-do-next": "" }, "get-field-override-categories": { @@ -3306,9 +3332,13 @@ "make-editable": "" }, "minimalistic-pagination": { + "page-count": "", "tooltip-next": "", "tooltip-previous": "" }, + "new-dashboard-with-ds": { + "not-found": "" + }, "new-panel": { "configure-button": "", "configure-button-menu": "", @@ -3371,9 +3401,12 @@ "go-back-to-queries": "" }, "public-dashboard-not-available": { + "does-not-exist": "", + "paused": "", "try-again-later": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, "row-options-button": { @@ -3435,6 +3468,8 @@ "description": "", "label-copy-tags": "", "label-folder": "", + "save": "", + "saving": "", "title": "" }, "save-dashboard-button": { @@ -3451,19 +3486,30 @@ "label-details": "" }, "save-dashboard-error-proxy": { - "title-conflict": "" + "body-name-exists": "", + "body-version-mismatch": "", + "title-name-exists": "", + "title-version-mismatch": "" }, "save-dashboard-form": { "cancel": "", "label-current-range-dashboard-default": "", "label-current-variable-values-dashboard-default": "", "no-changes-to-save": "", - "placeholder-describe-changes": "" + "placeholder-describe-changes": "", + "save": "", + "saving": "" }, "save-provisioned-dashboard-form": { "cancel": "", + "cannot-be-saved": "", "copy-json-to-clipboard": "", - "save-json-to-file": "" + "file-path": "", + "save-json-to-file": "", + "see-docs": "" + }, + "share-public-dashboard-loader": { + "loading-configuration": "" }, "solo-panel": { "loading-initializing-dashboard": "", @@ -3642,6 +3688,9 @@ "title-delete": "" }, "transformation-picker": { + "info": "", + "info-graph-not-suitable": "", + "info-switch-to-table": "", "placeholder-search-for-transformation": "", "read-more": "", "title-transformations": "" @@ -3666,6 +3715,9 @@ "discard": "", "title-unsaved-changes": "" }, + "untheme-dashboard-row": { + "dashboard-datasource": "" + }, "unthemed-dashboard-row": { "aria-label-delete-row": "", "learn-more": "" @@ -3677,7 +3729,10 @@ "tags-expected-strings": "etiquetas: matriz de cadenas prevista" }, "version-history-comparison": { - "label-view-json-diff": "" + "button-restore": "", + "label-view-json-diff": "", + "new-updated-by": "", + "old-updated-by": "" }, "version-history-table": { "aria-label-toggle-selection": "", @@ -3776,8 +3831,10 @@ "title-annotation-support-source": "" }, "annotation-settings-list": { + "built-in": "", "data-source": "", "delete-aria-label": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3838,6 +3895,8 @@ }, "data-source-variable-form": { "data-source-options": "", + "description-instance-name-filter": "", + "example-instance-name-filter": "", "selection-options": "" }, "description-label": { @@ -3898,7 +3957,13 @@ "title-plugin-dashboard": "", "title-someone-else-has-updated-this-dashboard": "", "would-still-dashboard": "" - } + }, + "save-and-overwrite": "" + }, + "library-viz-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_other": "" }, "name-already-exists-error": { "body-name-already-exists": "", @@ -3908,6 +3973,7 @@ "alert": { "title-errors-loading-rules": "" }, + "error-failed-to-load": "", "text-loading-rules": "", "title-dashboard-not-saved": "" }, @@ -3937,7 +4003,10 @@ "title-close": "" }, "provisioned-delete-modal": { + "cannot-be-deleted": "", + "file-path": "", "ok": "", + "see-docs": "", "title-cannot-delete-provisioned-dashboard": "" }, "provisioned-resource-delete-modal": { @@ -3950,13 +4019,21 @@ "query": "" }, "query-variable-editor-form": { + "description-examples": "", + "description-optional": "", "label-data-source": "", "query-options": "", "selection-options": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, + "save-button": { + "save": "", + "save-and-overwrite": "", + "saving": "" + }, "save-dashboard-as-form": { "aria-label-save-dashboard-description-field": "", "aria-label-save-dashboard-title-field": "", @@ -4009,17 +4086,21 @@ "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "affected-dashboards_one": "", + "affected-dashboards_other": "" }, "save-provisioned-dashboard-form": { "api-error": "Error al guardar el panel de control", "api-success": "Cambios en el panel de control guardados", "cancel": "", + "cannot-be-saved": "", "copy-json-message": "Si tienes acceso directo al destino, copia el JSON y pégalo allí.", "copy-json-to-clipboard": "", "dashboard-comment-placeholder-describe-changes-optional": "Añade una nota para describir tus cambios (opcional)", "description-branch-name-in-git-hub": "Nombre de la rama en GitHub", "description-inside-repository": "Ruta del archivo dentro del repositorio (.json o .yaml)", + "file-path": "", "label-branch": "Rama", "label-comment": "Comentario", "label-description": "", @@ -4030,6 +4111,7 @@ "save": "", "save-json-to-file": "", "saving": "Guardando…", + "see-docs": "", "title-required": "El título del panel de control es obligatorio", "title-same-as-folder": "El nombre del panel de control no puede ser el mismo que el nombre de la carpeta", "title-this-repository-is-read-only": "Este repositorio es de solo lectura", @@ -4048,6 +4130,9 @@ "share-button": { "aria-label-sharedropdownmenu": "" }, + "solo-panel-page": { + "loading": "" + }, "text-box-variable-form": { "placeholder-default-value-if-any": "", "text-options": "" @@ -4107,10 +4192,18 @@ "preview-of-values": "", "show-more": "" }, + "version-history": { + "comparison": { + "button-restore": "" + } + }, "version-history-comparison": { - "label-view-json-diff": "" + "label-view-json-diff": "", + "new-version-updated": "", + "old-version-updated": "" }, "version-history-header": { + "compare-versions": "", "latest": "", "tooltip-reset-version": "" }, @@ -4286,6 +4379,7 @@ }, "button-row": { "delete": "", + "save-and-test": "", "test": "" }, "cloud-info-box": { @@ -4309,6 +4403,9 @@ "data-source-picker": { "aria-label-select-a-data-source": "" }, + "data-source-plugin-config-page": { + "page-not-found": "" + }, "data-source-plugin-state": { "plugin-state": "" }, @@ -4445,8 +4542,12 @@ } }, "explore": { + "accordian-logs": { + "footer": "" + }, "accordian-references": { - "references": "" + "references": "", + "view-linked-span": "" }, "add-to-dashboard": "Añadir al tablero", "basic-extensions": { @@ -4455,6 +4556,7 @@ "clear": "", "confirm-navigation-modal": { "cancel": "", + "new-tab": "", "open": "", "open-in-new-tab": "" }, @@ -4475,9 +4577,14 @@ "add-transformation": "", "aria-label-delete-transformation": "", "aria-label-edit-transformation": "", + "body-correlation-details": "", + "expression": "", "label-description": "", + "label-description-header": "", "label-label": "", - "title-correlation-details": "" + "title-correlation-details": "", + "tooltip-transformations": "", + "transformations": "" }, "correlation-transformation-add-modal": { "add-transformation": "", @@ -4503,10 +4610,13 @@ "title-unsaved-changes-to-correlation": "" }, "draggable-manager-demo": { + "click-and-drag-gray-divider": "", "click-horizontally-somewhere-colored-below": "", + "drag-value": "", "draggable-manager-demo": "", "dragging-a-divider": "", - "dragging-a-sub-region": "" + "dragging-a-sub-region": "", + "value": "" }, "drilldownInfo": { "action": "Ir a Grafana Drilldown", @@ -4537,6 +4647,10 @@ "outline": "", "tooltip-content-outline": "" }, + "feature-toggle-page": { + "description-explore-disabled": "", + "title-explore-disabled": "" + }, "get-matches-metadata": { "matches": "" }, @@ -4547,10 +4661,15 @@ }, "live-logs": { "clear-logs": "", - "exit-live-mode": "" + "exit-live-mode": "", + "last-line-received": "", + "pause": "", + "resume": "" }, "live-tail-button": { + "live": "", "pause-the-live-stream": "", + "paused": "", "start-live-stream-your-logs": "", "stop-and-exit-the-live-stream": "" }, @@ -4579,12 +4698,14 @@ "show-original-line": "" }, "logs-sample-panel": { + "label": "", "logs-sample-is-loading": "", "no-logs-sample-data": "", "open-in-split-view-button": { "open-logs-in-split-view": "" }, - "title-failed-sample-query": "" + "title-failed-sample-query": "", + "tooltip": "" }, "logs-table-available-fields": { "title-label-percentage": "" @@ -4593,7 +4714,9 @@ "no-fields": "" }, "logs-table-multi-select": { - "fields": "" + "fields": "", + "reset": "", + "selected-fields": "" }, "logs-table-nav-field": { "aria-label-drag-and-drop-icon": "", @@ -4612,13 +4735,20 @@ "title-showing-partial-data": "", "title-unable-to-show-log-volume": "" }, + "logs-volumne-panel-list": { + "body-no-logs-volume-available": "" + }, "make-datasource-setup": { "aria-label-query": "" }, "next": "", "next-prev-result": { "aria-label-next": "", - "aria-label-prev": "" + "aria-label-prev": "", + "depth": "", + "depth-span-filter-matches": "", + "services": "", + "services-span-filter-matches": "" }, "no-data-source-call-to-action": { "cta-element": { @@ -4634,6 +4764,7 @@ "aria-label-go-queryless": "" }, "raw-list-container": { + "item-count": "", "label-expand-results": "" }, "raw-list-item": { @@ -4649,7 +4780,6 @@ "newest-first": "El más reciente primero", "oldest-first": "El más antiguo primero", "query-history": "Historial de consultas", - "query-library": "Biblioteca de consultas", "settings": "Configuración", "starred": "Destacado" }, @@ -4765,6 +4895,9 @@ }, "show-critical-path-only": "", "show-matches-only": "", + "span-bar": { + "tooltip-critical-path": "" + }, "span-bar-section": { "description-link": "", "title-span-bar": "" @@ -4791,6 +4924,7 @@ "aria-label-select-service-name-operator": "", "aria-label-select-span-name": "", "aria-label-select-span-name-operator": "", + "label-collapse": "", "label-duration": "", "label-service-name": "", "label-span-name": "", @@ -4834,6 +4968,9 @@ "tooltip-expand": "", "tooltip-expand-all": "" }, + "timeline-header-row": { + "title": "" + }, "toolbar": { "add-to-extensions": "", "add-to-queryless-extensions": "Ir sin consulta", @@ -4863,7 +5000,8 @@ "title-share-thoughts-about-tracing-grafana": "" }, "trace-page-header": { - "text-partial-trace": "" + "text-partial-trace": "", + "tooltip-url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "", @@ -4877,6 +5015,7 @@ "title-trace": "" }, "unconnected-node-graph-container": { + "count-warning": "", "title-node-graph": "" }, "unthemed-logs": { @@ -4930,11 +5069,20 @@ "classic-conditions": { "label-conditions": "" }, + "condition": { + "of": "", + "to": "", + "when": "" + }, "expression-query-editor": { "label-operation": "" }, "math": { - "available-math-functions": "" + "available-math-functions": "", + "run-math-operations": "", + "tooltip-footer": "", + "tooltip-title": "", + "tooltip-trigger": "" }, "reduce": { "label-function": "", @@ -5013,6 +5161,12 @@ }, "essentials": { "title-essentials": "" + }, + "progress-bar": { + "steps-status": "" + }, + "progress-status": { + "your-progress": "" } }, "grafana": { @@ -5404,8 +5558,14 @@ }, "inspector": { "inspect-data-tab": { + "loading": "", "no-data": "" }, + "inspect-error-tab": { + "error-status-message": "", + "error-status-no-message": "", + "error-trace-message": "" + }, "inspect-jsontab": { "apply": "", "support": "" @@ -5420,7 +5580,11 @@ }, "query-inspector": { "query-inspector": "", - "text-loading-query-inspector": "" + "text-loading-query-inspector": "", + "count-frames_one": "", + "count-frames_other": "", + "count-rows_one": "", + "count-rows_other": "" } }, "invites": { @@ -5432,6 +5596,11 @@ "name": "" }, "signup-invited-page": { + "complete-following": "", + "custom-has-invited-you": "", + "default-has-invited-you": "", + "greeting-custom": "", + "greeting-default": "", "label-email": "", "label-name": "", "label-password": "", @@ -5627,6 +5796,11 @@ "has-connected-dashboards": { "dashboard-name": "" }, + "library-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_other": "" + }, "library-panels-search": { "placeholder-search-by-name-or-description": "" }, @@ -5653,12 +5827,15 @@ "success": "Panel de librería guardado" }, "save-library-panel-modal": { + "affected-dashboards": "", "cancel": "", "dashboard-name": "", "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "num-affected_one": "", + "num-affected_other": "" } }, "link": { @@ -5683,6 +5860,7 @@ "live": { "dashboard-changed-modal": { "continue-editing": "", + "description": "", "discard-local-changes": "", "title-dashboard-changed": "" }, @@ -5761,6 +5939,9 @@ "collapse": "", "expand": "" }, + "log-labels-list": { + "log-line": "" + }, "log-line-menu": { "copy-link": "Copiar enlace a la línea de registro", "copy-log": "Copiar línea de registro", @@ -5850,6 +6031,8 @@ "aria-label-no-details": "" }, "un-themed-log-details-row": { + "filter-out": "", + "filter-out-query": "", "title-copy-value-to-clipboard": "", "toggle-field-button": { "tooltip-field-instead-message": "", @@ -5857,6 +6040,10 @@ }, "tooltip-adhoc-statistics": "", "tooltip-hide-adhoc-statistics": "" + }, + "un-themed-log-label-stats": { + "field-log-stats": "", + "label-log-stats": "" } }, "manage-dashbaords": { @@ -5878,6 +6065,7 @@ "options": "" }, "import-dashboard-overview-un-connected": { + "importing-from": "", "published-by": "", "updated-on": "" }, @@ -5937,6 +6125,9 @@ "button": "Migrar esta instancia a Cloud", "header": "Déjanos gestionar tu pila de Grafana" }, + "dashboard-info": { + "dashboard": "" + }, "delete-migration-token-confirm": { "body": "", "confirm-button": "Eliminar token", @@ -5952,6 +6143,7 @@ "title": "Desconectar de la pila en la nube" }, "folder-info": { + "folder": "", "unable-to-load-folder": "" }, "get-started": { @@ -6574,6 +6766,7 @@ "org": { "new-org-page": { "create": "", + "description": "", "label-organization-name": "", "placeholder-org-name": "" }, @@ -6582,6 +6775,9 @@ "label-organization-profile": "", "update-organization-name": "" }, + "select-org-page": { + "description": "" + }, "user-invite-form": { "back": "", "label-email-or-username": "", @@ -6589,7 +6785,11 @@ "label-send-invite-email": "", "placeholder-optional": "", "role": "", - "submit": "" + "submit": "", + "tooltip": "" + }, + "user-invite-page": { + "sub-title": "" } }, "org-picker": { @@ -6632,7 +6832,9 @@ "title-not-found": "" }, "panel-renderer": { + "failed-to-load-plugin": "", "loading-plugin-panel": "", + "no-panel-component": "", "no-panel-data": "" }, "panel-type-card": { @@ -6659,7 +6861,14 @@ "select-placeholder": "" }, "playlist": { + "playlist-table-rows": { + "aria-label-playlist-item": "", + "multiple-dashboards-found": "", + "no-dashboards-found": "", + "not-found": "" + }, "start-modal": { + "button-start": "", "description-customize-dashboard-elements-visibility": "", "description-panel-heights-adjusted-screen": "", "label-autofit": "", @@ -6719,6 +6928,11 @@ "message": "No se han encontrado listas de reproducción" } }, + "plugin": { + "plugin-details-deprecated-warning": { + "body-deprecated": "" + } + }, "plugins": { "app-root-page": { "access-denied": { @@ -6822,6 +7036,12 @@ "add-new-data-source": "", "title-button-disabled": "" }, + "install-controls": { + "install": "", + "installing": "", + "update": "", + "updating": "" + }, "install-controls-button": { "title-uninstall-modal": "" }, @@ -6840,6 +7060,7 @@ "label-severity": "" }, "not-found-plugin": { + "body-plugin-not-found": "", "title-plugin-not-found": "" }, "plugin-actions": { @@ -6854,12 +7075,17 @@ "tooltip-plugin-deprecated-longer-receives-updates": "" }, "plugin-details-body": { + "needs-service-account": "", "page-not-found": "" }, "plugin-details-deprecated-warning": { "title-deprecated": "" }, + "plugin-details-header-dependencies": { + "grafana-dependency": "" + }, "plugin-details-signature": { + "body-invalid-plugin-signature": "", "read-more-about-plugins-signing": "", "title-invalid-plugin-signature": "" }, @@ -6890,6 +7116,8 @@ "update-available": "" }, "plugin-usage": { + "body-missing-feature-toggle-panel-title-search": "", + "num-usages": "", "title-missing-feature-toggle-panel-title-search": "", "title-not-used-yet": "" }, @@ -6906,7 +7134,9 @@ }, "version-list": { "grafana-dependency": "", + "installed-version": "", "last-updated": "", + "latest-compatible-version": "", "no-version-history-was-found": "", "version": "" } @@ -6929,6 +7159,12 @@ }, "change-theme": "", "enable-kiosk-mode": "", + "feature-toggle-page": { + "profile-not-enabled": "" + }, + "feature-toggles-age": { + "enable-in-config": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -7073,6 +7309,8 @@ "repository-config-exists-configuration": "" }, "file-status-page": { + "save": "", + "saving": "", "title-error-loading-file": "" }, "files-view": { @@ -7103,7 +7341,6 @@ }, "getting-started": { "alert-temporary-outage": "", - "engaging-graphic": "", "modal-description-public-access": "", "modal-description-required-features": "", "modal-title-set-up-public-access": "", @@ -7199,13 +7436,16 @@ "checked": "", "finished": "", "health": "", + "healthy": "", "job-id": "", "last-ref": "", "messages": "", + "not-available": "", "pull-status": "", "resources": "", "started": "", "status": "", + "unhealthy": "", "view-folder": "", "webhook": "", "webhook-events": "", @@ -7496,31 +7736,41 @@ "hidden": "", "query-name-div-title-edit-query-name": "" }, + "query-error-alert": { + "trace-id": "" + }, "query-group": { "add-query": "", "expression": "", "title-data-source-help": "" }, "query-group-options-editor": { + "collapsed-interval": "", + "collapsed-max-data-points": "", "hide-time-info": "", "Query options-title-query-options": "", "relative-time": "", + "relative-time-tooltip": "", "render-cache-timeout-option": { "cache-timeout": "" }, "render-interval-option": { "interval": "", + "interval-tooltip": "", "min-interval": "", + "min-interval-tooltip": "", "time-range-max-data-points": "" }, "render-max-data-points-option": { "max-data-points": "", + "max-data-points-tooltip": "", "width-of-panel": "" }, "render-query-caching-ttloption": { "cache-ttl": "" }, - "time-shift": "" + "time-shift": "", + "time-shift-tooltip": "" }, "query-group-top-section": { "data-source": "", @@ -7702,6 +7952,9 @@ } }, "search": { + "action-row": { + "panel-type": "" + }, "actions": { "include-panels": "Incluir paneles", "remove-datasource-filter": "Fuente de datos: {{datasource}}", @@ -7825,6 +8078,9 @@ "label-roles": "", "used-by": "" }, + "service-account-profile-row": { + "edit": "" + }, "service-account-role-row": { "aria-label-role": "" }, @@ -8349,6 +8605,7 @@ "generate-enum-values-from-data": "" }, "enum-mapping-row": { + "click-to-edit": "", "remove-enum-row-aria-label-delete-enum-row": "", "remove-enum-row-tooltip-delete": "" }, @@ -8403,10 +8660,13 @@ "label-format": "", "label-set-timezone": "", "label-time-field": "", + "tooltip-format": "", "tooltip-timezone-manually": "" }, "get-tooltips": { - "json-value": "" + "description": "", + "json-value": "", + "valid-paths": "" }, "group-by-field-configuration": { "placeholder-ignored": "", @@ -8623,6 +8883,7 @@ "message": "No se han encontrado usuarios" }, "token-revoked-modal": { + "auto-revoked": "", "resume-message": "", "sign-in": "", "title-you-have-been-automatically-signed-out": "" @@ -8717,7 +8978,8 @@ "general": "", "placeholder-descriptive-text": "", "placeholder-label-name": "", - "placeholder-variable-name": "" + "placeholder-variable-name": "", + "run-query": "" }, "variable-editor-list": { "definition": "", diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 3899f9d870e..3e8fe7b3026 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -1,6 +1,5 @@ { "_comment": "Le code constitue la référence principale pour les phrases en anglais. Celles-ci doivent être mises à jour directement dans les composants, et les pluriels supplémentaires doivent être spécifiés dans ce fichier.", - "{{pageStart}} - {{pageEnd}} of {{numPages}}": "", "access-control": { "add-permission": { "built-in-aria-label": "", @@ -969,7 +968,8 @@ "expression-result": { "aria-label-nextpage": "", "aria-label-previouspage": "", - "no-data": "" + "no-data": "", + "page-counter": "" }, "federated-rule-warning": { "experimental": "", @@ -2004,6 +2004,10 @@ "title": "Règles d'alerte" }, "rulerrule-loading-error": "Échec du chargement de la règle", + "toggle": { + "go-back-to-old-look": "", + "try-out-the-new-look": "" + }, "unknown-rule-type": "" }, "rule-list-errors": { @@ -2513,6 +2517,18 @@ "configuration-required": "", "refer-documentation-configure-authentication": "" }, + "fields": { + "allowed-groups-description": "", + "allowed-groups-description-oauth": "", + "api-url-description": "", + "team-ids-description": "", + "team-ids-description-oauth": "", + "team-ids-github": "", + "team-ids-other": "", + "teams-url-description": "", + "teams-url-description-oauth": "", + "use-pkce-description": "" + }, "provider-card": { "text-badge-enabled": "", "text-badge-not-enabled": "" @@ -2526,6 +2542,8 @@ "label-enabled": "", "reset-configuration": "", "reset-configuration-description": "", + "save": "", + "saving": "", "title-more-actions": "", "title-reset": "", "tooltip-more-actions": "" @@ -2671,6 +2689,9 @@ "canvas": { "not-found-display": { "not-found": "" + }, + "text-display": { + "double-click-to-set": "" } }, "carousel": { @@ -2947,7 +2968,9 @@ }, "annotation-settings-list": { "aria-label-delete": "", + "built-in": "", "data-source": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3060,6 +3083,7 @@ } }, "confirm-plugin-dashboard-save-modal": { + "body-plugin-dashboard": "", "cancel": "", "overwrite": "", "title-plugin-dashboard": "" @@ -3082,6 +3106,7 @@ } }, "dashboard-validation": { + "body-dashboard-failed-schema-validation": "", "title-checking-dashboard-validity": "", "title-dashboard-failed-schema-validation": "", "title-error-checking-dashboard-validity": "" @@ -3216,6 +3241,7 @@ }, "gen-aihistory": { "aria-label-send-custom-feedback": "", + "footer-text": "", "placeholder-tell-ai-what-to-do-next": "" }, "get-field-override-categories": { @@ -3306,9 +3332,13 @@ "make-editable": "" }, "minimalistic-pagination": { + "page-count": "", "tooltip-next": "", "tooltip-previous": "" }, + "new-dashboard-with-ds": { + "not-found": "" + }, "new-panel": { "configure-button": "", "configure-button-menu": "", @@ -3371,9 +3401,12 @@ "go-back-to-queries": "" }, "public-dashboard-not-available": { + "does-not-exist": "", + "paused": "", "try-again-later": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, "row-options-button": { @@ -3435,6 +3468,8 @@ "description": "", "label-copy-tags": "", "label-folder": "", + "save": "", + "saving": "", "title": "" }, "save-dashboard-button": { @@ -3451,19 +3486,30 @@ "label-details": "" }, "save-dashboard-error-proxy": { - "title-conflict": "" + "body-name-exists": "", + "body-version-mismatch": "", + "title-name-exists": "", + "title-version-mismatch": "" }, "save-dashboard-form": { "cancel": "", "label-current-range-dashboard-default": "", "label-current-variable-values-dashboard-default": "", "no-changes-to-save": "", - "placeholder-describe-changes": "" + "placeholder-describe-changes": "", + "save": "", + "saving": "" }, "save-provisioned-dashboard-form": { "cancel": "", + "cannot-be-saved": "", "copy-json-to-clipboard": "", - "save-json-to-file": "" + "file-path": "", + "save-json-to-file": "", + "see-docs": "" + }, + "share-public-dashboard-loader": { + "loading-configuration": "" }, "solo-panel": { "loading-initializing-dashboard": "", @@ -3642,6 +3688,9 @@ "title-delete": "" }, "transformation-picker": { + "info": "", + "info-graph-not-suitable": "", + "info-switch-to-table": "", "placeholder-search-for-transformation": "", "read-more": "", "title-transformations": "" @@ -3666,6 +3715,9 @@ "discard": "", "title-unsaved-changes": "" }, + "untheme-dashboard-row": { + "dashboard-datasource": "" + }, "unthemed-dashboard-row": { "aria-label-delete-row": "", "learn-more": "" @@ -3677,7 +3729,10 @@ "tags-expected-strings": "balises attendues tableau de chaînes" }, "version-history-comparison": { - "label-view-json-diff": "" + "button-restore": "", + "label-view-json-diff": "", + "new-updated-by": "", + "old-updated-by": "" }, "version-history-table": { "aria-label-toggle-selection": "", @@ -3776,8 +3831,10 @@ "title-annotation-support-source": "" }, "annotation-settings-list": { + "built-in": "", "data-source": "", "delete-aria-label": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3838,6 +3895,8 @@ }, "data-source-variable-form": { "data-source-options": "", + "description-instance-name-filter": "", + "example-instance-name-filter": "", "selection-options": "" }, "description-label": { @@ -3898,7 +3957,13 @@ "title-plugin-dashboard": "", "title-someone-else-has-updated-this-dashboard": "", "would-still-dashboard": "" - } + }, + "save-and-overwrite": "" + }, + "library-viz-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_other": "" }, "name-already-exists-error": { "body-name-already-exists": "", @@ -3908,6 +3973,7 @@ "alert": { "title-errors-loading-rules": "" }, + "error-failed-to-load": "", "text-loading-rules": "", "title-dashboard-not-saved": "" }, @@ -3937,7 +4003,10 @@ "title-close": "" }, "provisioned-delete-modal": { + "cannot-be-deleted": "", + "file-path": "", "ok": "", + "see-docs": "", "title-cannot-delete-provisioned-dashboard": "" }, "provisioned-resource-delete-modal": { @@ -3950,13 +4019,21 @@ "query": "" }, "query-variable-editor-form": { + "description-examples": "", + "description-optional": "", "label-data-source": "", "query-options": "", "selection-options": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, + "save-button": { + "save": "", + "save-and-overwrite": "", + "saving": "" + }, "save-dashboard-as-form": { "aria-label-save-dashboard-description-field": "", "aria-label-save-dashboard-title-field": "", @@ -4009,17 +4086,21 @@ "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "affected-dashboards_one": "", + "affected-dashboards_other": "" }, "save-provisioned-dashboard-form": { "api-error": "Erreur d'enregistrement du tableau de bord", "api-success": "Modifications du tableau de bord enregistrées", "cancel": "Annuler", + "cannot-be-saved": "", "copy-json-message": "Si vous avez un accès direct à la cible, copiez le JSON et collez-le à cet endroit.", "copy-json-to-clipboard": "", "dashboard-comment-placeholder-describe-changes-optional": "Ajoutez une note pour décrire vos modifications (facultatif)", "description-branch-name-in-git-hub": "Nom de la branche dans GitHub", "description-inside-repository": "Chemin d’accès au fichier dans le référentiel (.json ou .yaml)", + "file-path": "", "label-branch": "Branche", "label-comment": "Commentaire", "label-description": "Description", @@ -4030,6 +4111,7 @@ "save": "Enregistrer", "save-json-to-file": "", "saving": "Enregistrement en cours...", + "see-docs": "", "title-required": "Le titre du tableau de bord est requis", "title-same-as-folder": "Le nom du tableau de bord ne peut pas être le même que le nom du dossier", "title-this-repository-is-read-only": "Ce référentiel est en lecture seule", @@ -4048,6 +4130,9 @@ "share-button": { "aria-label-sharedropdownmenu": "" }, + "solo-panel-page": { + "loading": "" + }, "text-box-variable-form": { "placeholder-default-value-if-any": "", "text-options": "" @@ -4107,10 +4192,18 @@ "preview-of-values": "", "show-more": "" }, + "version-history": { + "comparison": { + "button-restore": "" + } + }, "version-history-comparison": { - "label-view-json-diff": "" + "label-view-json-diff": "", + "new-version-updated": "", + "old-version-updated": "" }, "version-history-header": { + "compare-versions": "", "latest": "", "tooltip-reset-version": "" }, @@ -4286,6 +4379,7 @@ }, "button-row": { "delete": "", + "save-and-test": "", "test": "" }, "cloud-info-box": { @@ -4309,6 +4403,9 @@ "data-source-picker": { "aria-label-select-a-data-source": "" }, + "data-source-plugin-config-page": { + "page-not-found": "" + }, "data-source-plugin-state": { "plugin-state": "" }, @@ -4445,8 +4542,12 @@ } }, "explore": { + "accordian-logs": { + "footer": "" + }, "accordian-references": { - "references": "" + "references": "", + "view-linked-span": "" }, "add-to-dashboard": "Ajouter au tableau de bord", "basic-extensions": { @@ -4455,6 +4556,7 @@ "clear": "", "confirm-navigation-modal": { "cancel": "", + "new-tab": "", "open": "", "open-in-new-tab": "" }, @@ -4475,9 +4577,14 @@ "add-transformation": "", "aria-label-delete-transformation": "", "aria-label-edit-transformation": "", + "body-correlation-details": "", + "expression": "", "label-description": "", + "label-description-header": "", "label-label": "", - "title-correlation-details": "" + "title-correlation-details": "", + "tooltip-transformations": "", + "transformations": "" }, "correlation-transformation-add-modal": { "add-transformation": "", @@ -4503,10 +4610,13 @@ "title-unsaved-changes-to-correlation": "" }, "draggable-manager-demo": { + "click-and-drag-gray-divider": "", "click-horizontally-somewhere-colored-below": "", + "drag-value": "", "draggable-manager-demo": "", "dragging-a-divider": "", - "dragging-a-sub-region": "" + "dragging-a-sub-region": "", + "value": "" }, "drilldownInfo": { "action": "Accéder à Grafana Drilldown", @@ -4537,6 +4647,10 @@ "outline": "", "tooltip-content-outline": "" }, + "feature-toggle-page": { + "description-explore-disabled": "", + "title-explore-disabled": "" + }, "get-matches-metadata": { "matches": "" }, @@ -4547,10 +4661,15 @@ }, "live-logs": { "clear-logs": "", - "exit-live-mode": "" + "exit-live-mode": "", + "last-line-received": "", + "pause": "", + "resume": "" }, "live-tail-button": { + "live": "", "pause-the-live-stream": "", + "paused": "", "start-live-stream-your-logs": "", "stop-and-exit-the-live-stream": "" }, @@ -4579,12 +4698,14 @@ "show-original-line": "" }, "logs-sample-panel": { + "label": "", "logs-sample-is-loading": "", "no-logs-sample-data": "", "open-in-split-view-button": { "open-logs-in-split-view": "" }, - "title-failed-sample-query": "" + "title-failed-sample-query": "", + "tooltip": "" }, "logs-table-available-fields": { "title-label-percentage": "" @@ -4593,7 +4714,9 @@ "no-fields": "" }, "logs-table-multi-select": { - "fields": "" + "fields": "", + "reset": "", + "selected-fields": "" }, "logs-table-nav-field": { "aria-label-drag-and-drop-icon": "", @@ -4612,13 +4735,20 @@ "title-showing-partial-data": "", "title-unable-to-show-log-volume": "" }, + "logs-volumne-panel-list": { + "body-no-logs-volume-available": "" + }, "make-datasource-setup": { "aria-label-query": "" }, "next": "", "next-prev-result": { "aria-label-next": "", - "aria-label-prev": "" + "aria-label-prev": "", + "depth": "", + "depth-span-filter-matches": "", + "services": "", + "services-span-filter-matches": "" }, "no-data-source-call-to-action": { "cta-element": { @@ -4634,6 +4764,7 @@ "aria-label-go-queryless": "" }, "raw-list-container": { + "item-count": "", "label-expand-results": "" }, "raw-list-item": { @@ -4649,7 +4780,6 @@ "newest-first": "Plus récent en premier", "oldest-first": "Plus ancien en premier", "query-history": "Historique des requêtes", - "query-library": "Bibliothèque de requêtes", "settings": "Paramètres", "starred": "Favoris" }, @@ -4765,6 +4895,9 @@ }, "show-critical-path-only": "", "show-matches-only": "", + "span-bar": { + "tooltip-critical-path": "" + }, "span-bar-section": { "description-link": "", "title-span-bar": "" @@ -4791,6 +4924,7 @@ "aria-label-select-service-name-operator": "", "aria-label-select-span-name": "", "aria-label-select-span-name-operator": "", + "label-collapse": "", "label-duration": "", "label-service-name": "", "label-span-name": "", @@ -4834,6 +4968,9 @@ "tooltip-expand": "", "tooltip-expand-all": "" }, + "timeline-header-row": { + "title": "" + }, "toolbar": { "add-to-extensions": "Ajouter", "add-to-queryless-extensions": "Passer en mode sans requête", @@ -4863,7 +5000,8 @@ "title-share-thoughts-about-tracing-grafana": "" }, "trace-page-header": { - "text-partial-trace": "" + "text-partial-trace": "", + "tooltip-url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "", @@ -4877,6 +5015,7 @@ "title-trace": "" }, "unconnected-node-graph-container": { + "count-warning": "", "title-node-graph": "" }, "unthemed-logs": { @@ -4930,11 +5069,20 @@ "classic-conditions": { "label-conditions": "" }, + "condition": { + "of": "", + "to": "", + "when": "" + }, "expression-query-editor": { "label-operation": "" }, "math": { - "available-math-functions": "" + "available-math-functions": "", + "run-math-operations": "", + "tooltip-footer": "", + "tooltip-title": "", + "tooltip-trigger": "" }, "reduce": { "label-function": "", @@ -5013,6 +5161,12 @@ }, "essentials": { "title-essentials": "" + }, + "progress-bar": { + "steps-status": "" + }, + "progress-status": { + "your-progress": "" } }, "grafana": { @@ -5404,8 +5558,14 @@ }, "inspector": { "inspect-data-tab": { + "loading": "", "no-data": "" }, + "inspect-error-tab": { + "error-status-message": "", + "error-status-no-message": "", + "error-trace-message": "" + }, "inspect-jsontab": { "apply": "", "support": "" @@ -5420,7 +5580,11 @@ }, "query-inspector": { "query-inspector": "", - "text-loading-query-inspector": "" + "text-loading-query-inspector": "", + "count-frames_one": "", + "count-frames_other": "", + "count-rows_one": "", + "count-rows_other": "" } }, "invites": { @@ -5432,6 +5596,11 @@ "name": "" }, "signup-invited-page": { + "complete-following": "", + "custom-has-invited-you": "", + "default-has-invited-you": "", + "greeting-custom": "", + "greeting-default": "", "label-email": "", "label-name": "", "label-password": "", @@ -5627,6 +5796,11 @@ "has-connected-dashboards": { "dashboard-name": "" }, + "library-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_other": "" + }, "library-panels-search": { "placeholder-search-by-name-or-description": "" }, @@ -5653,12 +5827,15 @@ "success": "Panneau de bibliothèque enregistré" }, "save-library-panel-modal": { + "affected-dashboards": "", "cancel": "", "dashboard-name": "", "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "num-affected_one": "", + "num-affected_other": "" } }, "link": { @@ -5683,6 +5860,7 @@ "live": { "dashboard-changed-modal": { "continue-editing": "", + "description": "", "discard-local-changes": "", "title-dashboard-changed": "" }, @@ -5761,6 +5939,9 @@ "collapse": "", "expand": "" }, + "log-labels-list": { + "log-line": "" + }, "log-line-menu": { "copy-link": "Copier le lien vers la ligne de journal", "copy-log": "Copier la ligne de journal", @@ -5850,6 +6031,8 @@ "aria-label-no-details": "" }, "un-themed-log-details-row": { + "filter-out": "", + "filter-out-query": "", "title-copy-value-to-clipboard": "", "toggle-field-button": { "tooltip-field-instead-message": "", @@ -5857,6 +6040,10 @@ }, "tooltip-adhoc-statistics": "", "tooltip-hide-adhoc-statistics": "" + }, + "un-themed-log-label-stats": { + "field-log-stats": "", + "label-log-stats": "" } }, "manage-dashbaords": { @@ -5878,6 +6065,7 @@ "options": "" }, "import-dashboard-overview-un-connected": { + "importing-from": "", "published-by": "", "updated-on": "" }, @@ -5937,6 +6125,9 @@ "button": "Migrer cette instance vers le cloud", "header": "Laissez-nous gérer votre pile Grafana" }, + "dashboard-info": { + "dashboard": "" + }, "delete-migration-token-confirm": { "body": "Si vous avez déjà utilisé ce jeton avec une installation autogérée, celle-ci ne pourra plus importer de contenu.", "confirm-button": "Supprimer le jeton", @@ -5952,6 +6143,7 @@ "title": "Se déconnecter de la pile cloud" }, "folder-info": { + "folder": "", "unable-to-load-folder": "" }, "get-started": { @@ -6574,6 +6766,7 @@ "org": { "new-org-page": { "create": "", + "description": "", "label-organization-name": "", "placeholder-org-name": "" }, @@ -6582,6 +6775,9 @@ "label-organization-profile": "", "update-organization-name": "" }, + "select-org-page": { + "description": "" + }, "user-invite-form": { "back": "", "label-email-or-username": "", @@ -6589,7 +6785,11 @@ "label-send-invite-email": "", "placeholder-optional": "", "role": "", - "submit": "" + "submit": "", + "tooltip": "" + }, + "user-invite-page": { + "sub-title": "" } }, "org-picker": { @@ -6632,7 +6832,9 @@ "title-not-found": "" }, "panel-renderer": { + "failed-to-load-plugin": "", "loading-plugin-panel": "", + "no-panel-component": "", "no-panel-data": "" }, "panel-type-card": { @@ -6659,7 +6861,14 @@ "select-placeholder": "" }, "playlist": { + "playlist-table-rows": { + "aria-label-playlist-item": "", + "multiple-dashboards-found": "", + "no-dashboards-found": "", + "not-found": "" + }, "start-modal": { + "button-start": "", "description-customize-dashboard-elements-visibility": "", "description-panel-heights-adjusted-screen": "", "label-autofit": "", @@ -6719,6 +6928,11 @@ "message": "Aucune playlist trouvée" } }, + "plugin": { + "plugin-details-deprecated-warning": { + "body-deprecated": "" + } + }, "plugins": { "app-root-page": { "access-denied": { @@ -6822,6 +7036,12 @@ "add-new-data-source": "", "title-button-disabled": "" }, + "install-controls": { + "install": "", + "installing": "", + "update": "", + "updating": "" + }, "install-controls-button": { "title-uninstall-modal": "" }, @@ -6840,6 +7060,7 @@ "label-severity": "" }, "not-found-plugin": { + "body-plugin-not-found": "", "title-plugin-not-found": "" }, "plugin-actions": { @@ -6854,12 +7075,17 @@ "tooltip-plugin-deprecated-longer-receives-updates": "" }, "plugin-details-body": { + "needs-service-account": "", "page-not-found": "" }, "plugin-details-deprecated-warning": { "title-deprecated": "" }, + "plugin-details-header-dependencies": { + "grafana-dependency": "" + }, "plugin-details-signature": { + "body-invalid-plugin-signature": "", "read-more-about-plugins-signing": "", "title-invalid-plugin-signature": "" }, @@ -6890,6 +7116,8 @@ "update-available": "" }, "plugin-usage": { + "body-missing-feature-toggle-panel-title-search": "", + "num-usages": "", "title-missing-feature-toggle-panel-title-search": "", "title-not-used-yet": "" }, @@ -6906,7 +7134,9 @@ }, "version-list": { "grafana-dependency": "", + "installed-version": "", "last-updated": "", + "latest-compatible-version": "", "no-version-history-was-found": "", "version": "" } @@ -6929,6 +7159,12 @@ }, "change-theme": "Modifier le thème", "enable-kiosk-mode": "", + "feature-toggle-page": { + "profile-not-enabled": "" + }, + "feature-toggles-age": { + "enable-in-config": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -7073,6 +7309,8 @@ "repository-config-exists-configuration": "" }, "file-status-page": { + "save": "", + "saving": "", "title-error-loading-file": "" }, "files-view": { @@ -7103,7 +7341,6 @@ }, "getting-started": { "alert-temporary-outage": "", - "engaging-graphic": "", "modal-description-public-access": "", "modal-description-required-features": "", "modal-title-set-up-public-access": "", @@ -7199,13 +7436,16 @@ "checked": "", "finished": "", "health": "", + "healthy": "", "job-id": "", "last-ref": "", "messages": "", + "not-available": "", "pull-status": "", "resources": "", "started": "", "status": "", + "unhealthy": "", "view-folder": "", "webhook": "", "webhook-events": "", @@ -7496,31 +7736,41 @@ "hidden": "", "query-name-div-title-edit-query-name": "" }, + "query-error-alert": { + "trace-id": "" + }, "query-group": { "add-query": "", "expression": "", "title-data-source-help": "" }, "query-group-options-editor": { + "collapsed-interval": "", + "collapsed-max-data-points": "", "hide-time-info": "", "Query options-title-query-options": "", "relative-time": "", + "relative-time-tooltip": "", "render-cache-timeout-option": { "cache-timeout": "" }, "render-interval-option": { "interval": "", + "interval-tooltip": "", "min-interval": "", + "min-interval-tooltip": "", "time-range-max-data-points": "" }, "render-max-data-points-option": { "max-data-points": "", + "max-data-points-tooltip": "", "width-of-panel": "" }, "render-query-caching-ttloption": { "cache-ttl": "" }, - "time-shift": "" + "time-shift": "", + "time-shift-tooltip": "" }, "query-group-top-section": { "data-source": "", @@ -7702,6 +7952,9 @@ } }, "search": { + "action-row": { + "panel-type": "" + }, "actions": { "include-panels": "Inclure des panneaux", "remove-datasource-filter": "Source de données : {{datasource}}", @@ -7825,6 +8078,9 @@ "label-roles": "", "used-by": "" }, + "service-account-profile-row": { + "edit": "" + }, "service-account-role-row": { "aria-label-role": "" }, @@ -8349,6 +8605,7 @@ "generate-enum-values-from-data": "" }, "enum-mapping-row": { + "click-to-edit": "", "remove-enum-row-aria-label-delete-enum-row": "", "remove-enum-row-tooltip-delete": "" }, @@ -8403,10 +8660,13 @@ "label-format": "", "label-set-timezone": "", "label-time-field": "", + "tooltip-format": "", "tooltip-timezone-manually": "" }, "get-tooltips": { - "json-value": "" + "description": "", + "json-value": "", + "valid-paths": "" }, "group-by-field-configuration": { "placeholder-ignored": "", @@ -8623,6 +8883,7 @@ "message": "Aucun utilisateur trouvé" }, "token-revoked-modal": { + "auto-revoked": "", "resume-message": "", "sign-in": "", "title-you-have-been-automatically-signed-out": "" @@ -8717,7 +8978,8 @@ "general": "", "placeholder-descriptive-text": "", "placeholder-label-name": "", - "placeholder-variable-name": "" + "placeholder-variable-name": "", + "run-query": "" }, "variable-editor-list": { "definition": "", diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index 0cb47b1d17e..49fadaec893 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -1,6 +1,5 @@ { "_comment": "A kód az angol kifejezések hitelességének forrása. Ezeket közvetlenül az összetevőkben kell frissíteni, és további többes számokat kell megadni ebben a fájlban.", - "{{pageStart}} - {{pageEnd}} of {{numPages}}": "", "access-control": { "add-permission": { "built-in-aria-label": "", @@ -969,7 +968,8 @@ "expression-result": { "aria-label-nextpage": "", "aria-label-previouspage": "", - "no-data": "" + "no-data": "", + "page-counter": "" }, "federated-rule-warning": { "experimental": "", @@ -2004,6 +2004,10 @@ "title": "Riasztási szabályok" }, "rulerrule-loading-error": "A szabály betöltése sikertelen volt", + "toggle": { + "go-back-to-old-look": "", + "try-out-the-new-look": "" + }, "unknown-rule-type": "" }, "rule-list-errors": { @@ -2513,6 +2517,18 @@ "configuration-required": "", "refer-documentation-configure-authentication": "" }, + "fields": { + "allowed-groups-description": "", + "allowed-groups-description-oauth": "", + "api-url-description": "", + "team-ids-description": "", + "team-ids-description-oauth": "", + "team-ids-github": "", + "team-ids-other": "", + "teams-url-description": "", + "teams-url-description-oauth": "", + "use-pkce-description": "" + }, "provider-card": { "text-badge-enabled": "", "text-badge-not-enabled": "" @@ -2526,6 +2542,8 @@ "label-enabled": "", "reset-configuration": "", "reset-configuration-description": "", + "save": "", + "saving": "", "title-more-actions": "", "title-reset": "", "tooltip-more-actions": "" @@ -2671,6 +2689,9 @@ "canvas": { "not-found-display": { "not-found": "" + }, + "text-display": { + "double-click-to-set": "" } }, "carousel": { @@ -2947,7 +2968,9 @@ }, "annotation-settings-list": { "aria-label-delete": "", + "built-in": "", "data-source": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3060,6 +3083,7 @@ } }, "confirm-plugin-dashboard-save-modal": { + "body-plugin-dashboard": "", "cancel": "", "overwrite": "", "title-plugin-dashboard": "" @@ -3082,6 +3106,7 @@ } }, "dashboard-validation": { + "body-dashboard-failed-schema-validation": "", "title-checking-dashboard-validity": "", "title-dashboard-failed-schema-validation": "", "title-error-checking-dashboard-validity": "" @@ -3216,6 +3241,7 @@ }, "gen-aihistory": { "aria-label-send-custom-feedback": "", + "footer-text": "", "placeholder-tell-ai-what-to-do-next": "" }, "get-field-override-categories": { @@ -3306,9 +3332,13 @@ "make-editable": "" }, "minimalistic-pagination": { + "page-count": "", "tooltip-next": "", "tooltip-previous": "" }, + "new-dashboard-with-ds": { + "not-found": "" + }, "new-panel": { "configure-button": "", "configure-button-menu": "", @@ -3371,9 +3401,12 @@ "go-back-to-queries": "" }, "public-dashboard-not-available": { + "does-not-exist": "", + "paused": "", "try-again-later": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, "row-options-button": { @@ -3435,6 +3468,8 @@ "description": "", "label-copy-tags": "", "label-folder": "", + "save": "", + "saving": "", "title": "" }, "save-dashboard-button": { @@ -3451,19 +3486,30 @@ "label-details": "" }, "save-dashboard-error-proxy": { - "title-conflict": "" + "body-name-exists": "", + "body-version-mismatch": "", + "title-name-exists": "", + "title-version-mismatch": "" }, "save-dashboard-form": { "cancel": "", "label-current-range-dashboard-default": "", "label-current-variable-values-dashboard-default": "", "no-changes-to-save": "", - "placeholder-describe-changes": "" + "placeholder-describe-changes": "", + "save": "", + "saving": "" }, "save-provisioned-dashboard-form": { "cancel": "", + "cannot-be-saved": "", "copy-json-to-clipboard": "", - "save-json-to-file": "" + "file-path": "", + "save-json-to-file": "", + "see-docs": "" + }, + "share-public-dashboard-loader": { + "loading-configuration": "" }, "solo-panel": { "loading-initializing-dashboard": "", @@ -3642,6 +3688,9 @@ "title-delete": "" }, "transformation-picker": { + "info": "", + "info-graph-not-suitable": "", + "info-switch-to-table": "", "placeholder-search-for-transformation": "", "read-more": "", "title-transformations": "" @@ -3666,6 +3715,9 @@ "discard": "", "title-unsaved-changes": "" }, + "untheme-dashboard-row": { + "dashboard-datasource": "" + }, "unthemed-dashboard-row": { "aria-label-delete-row": "", "learn-more": "" @@ -3677,7 +3729,10 @@ "tags-expected-strings": "címke karakterláncok tömbjét várta" }, "version-history-comparison": { - "label-view-json-diff": "" + "button-restore": "", + "label-view-json-diff": "", + "new-updated-by": "", + "old-updated-by": "" }, "version-history-table": { "aria-label-toggle-selection": "", @@ -3776,8 +3831,10 @@ "title-annotation-support-source": "" }, "annotation-settings-list": { + "built-in": "", "data-source": "", "delete-aria-label": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3838,6 +3895,8 @@ }, "data-source-variable-form": { "data-source-options": "", + "description-instance-name-filter": "", + "example-instance-name-filter": "", "selection-options": "" }, "description-label": { @@ -3898,7 +3957,13 @@ "title-plugin-dashboard": "", "title-someone-else-has-updated-this-dashboard": "", "would-still-dashboard": "" - } + }, + "save-and-overwrite": "" + }, + "library-viz-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_other": "" }, "name-already-exists-error": { "body-name-already-exists": "", @@ -3908,6 +3973,7 @@ "alert": { "title-errors-loading-rules": "" }, + "error-failed-to-load": "", "text-loading-rules": "", "title-dashboard-not-saved": "" }, @@ -3937,7 +4003,10 @@ "title-close": "" }, "provisioned-delete-modal": { + "cannot-be-deleted": "", + "file-path": "", "ok": "", + "see-docs": "", "title-cannot-delete-provisioned-dashboard": "" }, "provisioned-resource-delete-modal": { @@ -3950,13 +4019,21 @@ "query": "" }, "query-variable-editor-form": { + "description-examples": "", + "description-optional": "", "label-data-source": "", "query-options": "", "selection-options": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, + "save-button": { + "save": "", + "save-and-overwrite": "", + "saving": "" + }, "save-dashboard-as-form": { "aria-label-save-dashboard-description-field": "", "aria-label-save-dashboard-title-field": "", @@ -4009,17 +4086,21 @@ "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "affected-dashboards_one": "", + "affected-dashboards_other": "" }, "save-provisioned-dashboard-form": { "api-error": "Hiba történt az irányítópult mentésekor", "api-success": "Irányítópult-módosítások mentve", "cancel": "Mégse", + "cannot-be-saved": "", "copy-json-message": "Ha közvetlen hozzáférése van a célhoz, másolja ki a JSON-kódot, és illessze be oda.", "copy-json-to-clipboard": "", "dashboard-comment-placeholder-describe-changes-optional": "Megjegyzés hozzáadása a módosítások leírásához (nem kötelező)", "description-branch-name-in-git-hub": "Ág neve a GitHubon", "description-inside-repository": "Fájl útvonala az adattáron belül (.json vagy .yaml)", + "file-path": "", "label-branch": "Ág", "label-comment": "Megjegyzés", "label-description": "Leírás", @@ -4030,6 +4111,7 @@ "save": "Mentés", "save-json-to-file": "", "saving": "Mentés...", + "see-docs": "", "title-required": "Az irányítópult címének megadása kötelező", "title-same-as-folder": "Az irányítópult neve nem lehet ugyanaz, mint a mappa neve", "title-this-repository-is-read-only": "Ez az adattár csak olvasható", @@ -4048,6 +4130,9 @@ "share-button": { "aria-label-sharedropdownmenu": "" }, + "solo-panel-page": { + "loading": "" + }, "text-box-variable-form": { "placeholder-default-value-if-any": "", "text-options": "" @@ -4107,10 +4192,18 @@ "preview-of-values": "", "show-more": "" }, + "version-history": { + "comparison": { + "button-restore": "" + } + }, "version-history-comparison": { - "label-view-json-diff": "" + "label-view-json-diff": "", + "new-version-updated": "", + "old-version-updated": "" }, "version-history-header": { + "compare-versions": "", "latest": "", "tooltip-reset-version": "" }, @@ -4286,6 +4379,7 @@ }, "button-row": { "delete": "", + "save-and-test": "", "test": "" }, "cloud-info-box": { @@ -4309,6 +4403,9 @@ "data-source-picker": { "aria-label-select-a-data-source": "" }, + "data-source-plugin-config-page": { + "page-not-found": "" + }, "data-source-plugin-state": { "plugin-state": "" }, @@ -4445,8 +4542,12 @@ } }, "explore": { + "accordian-logs": { + "footer": "" + }, "accordian-references": { - "references": "" + "references": "", + "view-linked-span": "" }, "add-to-dashboard": "Hozzáadás az irányítópulthoz", "basic-extensions": { @@ -4455,6 +4556,7 @@ "clear": "", "confirm-navigation-modal": { "cancel": "", + "new-tab": "", "open": "", "open-in-new-tab": "" }, @@ -4475,9 +4577,14 @@ "add-transformation": "", "aria-label-delete-transformation": "", "aria-label-edit-transformation": "", + "body-correlation-details": "", + "expression": "", "label-description": "", + "label-description-header": "", "label-label": "", - "title-correlation-details": "" + "title-correlation-details": "", + "tooltip-transformations": "", + "transformations": "" }, "correlation-transformation-add-modal": { "add-transformation": "", @@ -4503,10 +4610,13 @@ "title-unsaved-changes-to-correlation": "" }, "draggable-manager-demo": { + "click-and-drag-gray-divider": "", "click-horizontally-somewhere-colored-below": "", + "drag-value": "", "draggable-manager-demo": "", "dragging-a-divider": "", - "dragging-a-sub-region": "" + "dragging-a-sub-region": "", + "value": "" }, "drilldownInfo": { "action": "Ugrás a Grafana Drilldownhoz", @@ -4537,6 +4647,10 @@ "outline": "", "tooltip-content-outline": "" }, + "feature-toggle-page": { + "description-explore-disabled": "", + "title-explore-disabled": "" + }, "get-matches-metadata": { "matches": "" }, @@ -4547,10 +4661,15 @@ }, "live-logs": { "clear-logs": "", - "exit-live-mode": "" + "exit-live-mode": "", + "last-line-received": "", + "pause": "", + "resume": "" }, "live-tail-button": { + "live": "", "pause-the-live-stream": "", + "paused": "", "start-live-stream-your-logs": "", "stop-and-exit-the-live-stream": "" }, @@ -4579,12 +4698,14 @@ "show-original-line": "" }, "logs-sample-panel": { + "label": "", "logs-sample-is-loading": "", "no-logs-sample-data": "", "open-in-split-view-button": { "open-logs-in-split-view": "" }, - "title-failed-sample-query": "" + "title-failed-sample-query": "", + "tooltip": "" }, "logs-table-available-fields": { "title-label-percentage": "" @@ -4593,7 +4714,9 @@ "no-fields": "" }, "logs-table-multi-select": { - "fields": "" + "fields": "", + "reset": "", + "selected-fields": "" }, "logs-table-nav-field": { "aria-label-drag-and-drop-icon": "", @@ -4612,13 +4735,20 @@ "title-showing-partial-data": "", "title-unable-to-show-log-volume": "" }, + "logs-volumne-panel-list": { + "body-no-logs-volume-available": "" + }, "make-datasource-setup": { "aria-label-query": "" }, "next": "", "next-prev-result": { "aria-label-next": "", - "aria-label-prev": "" + "aria-label-prev": "", + "depth": "", + "depth-span-filter-matches": "", + "services": "", + "services-span-filter-matches": "" }, "no-data-source-call-to-action": { "cta-element": { @@ -4634,6 +4764,7 @@ "aria-label-go-queryless": "" }, "raw-list-container": { + "item-count": "", "label-expand-results": "" }, "raw-list-item": { @@ -4649,7 +4780,6 @@ "newest-first": "Újabbat előre", "oldest-first": "Régebbit előre", "query-history": "Lekérdezési előzmények", - "query-library": "Lekérdezési könyvtár", "settings": "Beállítások", "starred": "Csillagozott" }, @@ -4765,6 +4895,9 @@ }, "show-critical-path-only": "", "show-matches-only": "", + "span-bar": { + "tooltip-critical-path": "" + }, "span-bar-section": { "description-link": "", "title-span-bar": "" @@ -4791,6 +4924,7 @@ "aria-label-select-service-name-operator": "", "aria-label-select-span-name": "", "aria-label-select-span-name-operator": "", + "label-collapse": "", "label-duration": "", "label-service-name": "", "label-span-name": "", @@ -4834,6 +4968,9 @@ "tooltip-expand": "", "tooltip-expand-all": "" }, + "timeline-header-row": { + "title": "" + }, "toolbar": { "add-to-extensions": "Hozzáadás", "add-to-queryless-extensions": "Lekérdezés nélkül", @@ -4863,7 +5000,8 @@ "title-share-thoughts-about-tracing-grafana": "" }, "trace-page-header": { - "text-partial-trace": "" + "text-partial-trace": "", + "tooltip-url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "", @@ -4877,6 +5015,7 @@ "title-trace": "" }, "unconnected-node-graph-container": { + "count-warning": "", "title-node-graph": "" }, "unthemed-logs": { @@ -4930,11 +5069,20 @@ "classic-conditions": { "label-conditions": "" }, + "condition": { + "of": "", + "to": "", + "when": "" + }, "expression-query-editor": { "label-operation": "" }, "math": { - "available-math-functions": "" + "available-math-functions": "", + "run-math-operations": "", + "tooltip-footer": "", + "tooltip-title": "", + "tooltip-trigger": "" }, "reduce": { "label-function": "", @@ -5013,6 +5161,12 @@ }, "essentials": { "title-essentials": "" + }, + "progress-bar": { + "steps-status": "" + }, + "progress-status": { + "your-progress": "" } }, "grafana": { @@ -5404,8 +5558,14 @@ }, "inspector": { "inspect-data-tab": { + "loading": "", "no-data": "" }, + "inspect-error-tab": { + "error-status-message": "", + "error-status-no-message": "", + "error-trace-message": "" + }, "inspect-jsontab": { "apply": "", "support": "" @@ -5420,7 +5580,11 @@ }, "query-inspector": { "query-inspector": "", - "text-loading-query-inspector": "" + "text-loading-query-inspector": "", + "count-frames_one": "", + "count-frames_other": "", + "count-rows_one": "", + "count-rows_other": "" } }, "invites": { @@ -5432,6 +5596,11 @@ "name": "" }, "signup-invited-page": { + "complete-following": "", + "custom-has-invited-you": "", + "default-has-invited-you": "", + "greeting-custom": "", + "greeting-default": "", "label-email": "", "label-name": "", "label-password": "", @@ -5627,6 +5796,11 @@ "has-connected-dashboards": { "dashboard-name": "" }, + "library-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_other": "" + }, "library-panels-search": { "placeholder-search-by-name-or-description": "" }, @@ -5653,12 +5827,15 @@ "success": "Könyvtárpanel mentve" }, "save-library-panel-modal": { + "affected-dashboards": "", "cancel": "", "dashboard-name": "", "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "num-affected_one": "", + "num-affected_other": "" } }, "link": { @@ -5683,6 +5860,7 @@ "live": { "dashboard-changed-modal": { "continue-editing": "", + "description": "", "discard-local-changes": "", "title-dashboard-changed": "" }, @@ -5761,6 +5939,9 @@ "collapse": "", "expand": "" }, + "log-labels-list": { + "log-line": "" + }, "log-line-menu": { "copy-link": "Hivatkozás másolása a naplósorba", "copy-log": "Naplósor másolása", @@ -5850,6 +6031,8 @@ "aria-label-no-details": "" }, "un-themed-log-details-row": { + "filter-out": "", + "filter-out-query": "", "title-copy-value-to-clipboard": "", "toggle-field-button": { "tooltip-field-instead-message": "", @@ -5857,6 +6040,10 @@ }, "tooltip-adhoc-statistics": "", "tooltip-hide-adhoc-statistics": "" + }, + "un-themed-log-label-stats": { + "field-log-stats": "", + "label-log-stats": "" } }, "manage-dashbaords": { @@ -5878,6 +6065,7 @@ "options": "" }, "import-dashboard-overview-un-connected": { + "importing-from": "", "published-by": "", "updated-on": "" }, @@ -5937,6 +6125,9 @@ "button": "Példány áttelepítése a felhőbe", "header": "Bízza ránk a Grafana-stack kezelését" }, + "dashboard-info": { + "dashboard": "" + }, "delete-migration-token-confirm": { "body": "Ha ezt a tokent már használta egy saját kezelésű telepítésnél, akkor az a telepítés már nem tud tartalmat feltölteni.", "confirm-button": "Token törlése", @@ -5952,6 +6143,7 @@ "title": "Leválasztás a felhőstackről" }, "folder-info": { + "folder": "", "unable-to-load-folder": "" }, "get-started": { @@ -6574,6 +6766,7 @@ "org": { "new-org-page": { "create": "", + "description": "", "label-organization-name": "", "placeholder-org-name": "" }, @@ -6582,6 +6775,9 @@ "label-organization-profile": "", "update-organization-name": "" }, + "select-org-page": { + "description": "" + }, "user-invite-form": { "back": "", "label-email-or-username": "", @@ -6589,7 +6785,11 @@ "label-send-invite-email": "", "placeholder-optional": "", "role": "", - "submit": "" + "submit": "", + "tooltip": "" + }, + "user-invite-page": { + "sub-title": "" } }, "org-picker": { @@ -6632,7 +6832,9 @@ "title-not-found": "" }, "panel-renderer": { + "failed-to-load-plugin": "", "loading-plugin-panel": "", + "no-panel-component": "", "no-panel-data": "" }, "panel-type-card": { @@ -6659,7 +6861,14 @@ "select-placeholder": "" }, "playlist": { + "playlist-table-rows": { + "aria-label-playlist-item": "", + "multiple-dashboards-found": "", + "no-dashboards-found": "", + "not-found": "" + }, "start-modal": { + "button-start": "", "description-customize-dashboard-elements-visibility": "", "description-panel-heights-adjusted-screen": "", "label-autofit": "", @@ -6719,6 +6928,11 @@ "message": "Nem található lejátszási lista" } }, + "plugin": { + "plugin-details-deprecated-warning": { + "body-deprecated": "" + } + }, "plugins": { "app-root-page": { "access-denied": { @@ -6822,6 +7036,12 @@ "add-new-data-source": "", "title-button-disabled": "" }, + "install-controls": { + "install": "", + "installing": "", + "update": "", + "updating": "" + }, "install-controls-button": { "title-uninstall-modal": "" }, @@ -6840,6 +7060,7 @@ "label-severity": "" }, "not-found-plugin": { + "body-plugin-not-found": "", "title-plugin-not-found": "" }, "plugin-actions": { @@ -6854,12 +7075,17 @@ "tooltip-plugin-deprecated-longer-receives-updates": "" }, "plugin-details-body": { + "needs-service-account": "", "page-not-found": "" }, "plugin-details-deprecated-warning": { "title-deprecated": "" }, + "plugin-details-header-dependencies": { + "grafana-dependency": "" + }, "plugin-details-signature": { + "body-invalid-plugin-signature": "", "read-more-about-plugins-signing": "", "title-invalid-plugin-signature": "" }, @@ -6890,6 +7116,8 @@ "update-available": "" }, "plugin-usage": { + "body-missing-feature-toggle-panel-title-search": "", + "num-usages": "", "title-missing-feature-toggle-panel-title-search": "", "title-not-used-yet": "" }, @@ -6906,7 +7134,9 @@ }, "version-list": { "grafana-dependency": "", + "installed-version": "", "last-updated": "", + "latest-compatible-version": "", "no-version-history-was-found": "", "version": "" } @@ -6929,6 +7159,12 @@ }, "change-theme": "Téma módosítása", "enable-kiosk-mode": "", + "feature-toggle-page": { + "profile-not-enabled": "" + }, + "feature-toggles-age": { + "enable-in-config": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -7073,6 +7309,8 @@ "repository-config-exists-configuration": "" }, "file-status-page": { + "save": "", + "saving": "", "title-error-loading-file": "" }, "files-view": { @@ -7103,7 +7341,6 @@ }, "getting-started": { "alert-temporary-outage": "", - "engaging-graphic": "", "modal-description-public-access": "", "modal-description-required-features": "", "modal-title-set-up-public-access": "", @@ -7199,13 +7436,16 @@ "checked": "", "finished": "", "health": "", + "healthy": "", "job-id": "", "last-ref": "", "messages": "", + "not-available": "", "pull-status": "", "resources": "", "started": "", "status": "", + "unhealthy": "", "view-folder": "", "webhook": "", "webhook-events": "", @@ -7496,31 +7736,41 @@ "hidden": "", "query-name-div-title-edit-query-name": "" }, + "query-error-alert": { + "trace-id": "" + }, "query-group": { "add-query": "", "expression": "", "title-data-source-help": "" }, "query-group-options-editor": { + "collapsed-interval": "", + "collapsed-max-data-points": "", "hide-time-info": "", "Query options-title-query-options": "", "relative-time": "", + "relative-time-tooltip": "", "render-cache-timeout-option": { "cache-timeout": "" }, "render-interval-option": { "interval": "", + "interval-tooltip": "", "min-interval": "", + "min-interval-tooltip": "", "time-range-max-data-points": "" }, "render-max-data-points-option": { "max-data-points": "", + "max-data-points-tooltip": "", "width-of-panel": "" }, "render-query-caching-ttloption": { "cache-ttl": "" }, - "time-shift": "" + "time-shift": "", + "time-shift-tooltip": "" }, "query-group-top-section": { "data-source": "", @@ -7702,6 +7952,9 @@ } }, "search": { + "action-row": { + "panel-type": "" + }, "actions": { "include-panels": "Panelekkel együtt", "remove-datasource-filter": "Adatforrás: {{datasource}}", @@ -7825,6 +8078,9 @@ "label-roles": "", "used-by": "" }, + "service-account-profile-row": { + "edit": "" + }, "service-account-role-row": { "aria-label-role": "" }, @@ -8349,6 +8605,7 @@ "generate-enum-values-from-data": "" }, "enum-mapping-row": { + "click-to-edit": "", "remove-enum-row-aria-label-delete-enum-row": "", "remove-enum-row-tooltip-delete": "" }, @@ -8403,10 +8660,13 @@ "label-format": "", "label-set-timezone": "", "label-time-field": "", + "tooltip-format": "", "tooltip-timezone-manually": "" }, "get-tooltips": { - "json-value": "" + "description": "", + "json-value": "", + "valid-paths": "" }, "group-by-field-configuration": { "placeholder-ignored": "", @@ -8623,6 +8883,7 @@ "message": "Nincsenek felhasználók" }, "token-revoked-modal": { + "auto-revoked": "", "resume-message": "", "sign-in": "", "title-you-have-been-automatically-signed-out": "" @@ -8717,7 +8978,8 @@ "general": "", "placeholder-descriptive-text": "", "placeholder-label-name": "", - "placeholder-variable-name": "" + "placeholder-variable-name": "", + "run-query": "" }, "variable-editor-list": { "definition": "", diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index ea3063d0299..d6800eb23b0 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -1,6 +1,5 @@ { "_comment": "Kode ini adalah sumber kebenaran untuk frasa bahasa Inggris. Kode ini harus diperbarui dalam komponen secara langsung, dan bentuk jamak tambahan disebutkan dalam file ini.", - "{{pageStart}} - {{pageEnd}} of {{numPages}}": "", "access-control": { "add-permission": { "built-in-aria-label": "", @@ -963,7 +962,8 @@ "expression-result": { "aria-label-nextpage": "", "aria-label-previouspage": "", - "no-data": "" + "no-data": "", + "page-counter": "" }, "federated-rule-warning": { "experimental": "", @@ -1994,6 +1994,10 @@ "title": "Aturan peringatan" }, "rulerrule-loading-error": "Gagal memuat aturan", + "toggle": { + "go-back-to-old-look": "", + "try-out-the-new-look": "" + }, "unknown-rule-type": "" }, "rule-list-errors": { @@ -2500,6 +2504,18 @@ "configuration-required": "", "refer-documentation-configure-authentication": "" }, + "fields": { + "allowed-groups-description": "", + "allowed-groups-description-oauth": "", + "api-url-description": "", + "team-ids-description": "", + "team-ids-description-oauth": "", + "team-ids-github": "", + "team-ids-other": "", + "teams-url-description": "", + "teams-url-description-oauth": "", + "use-pkce-description": "" + }, "provider-card": { "text-badge-enabled": "", "text-badge-not-enabled": "" @@ -2513,6 +2529,8 @@ "label-enabled": "", "reset-configuration": "", "reset-configuration-description": "", + "save": "", + "saving": "", "title-more-actions": "", "title-reset": "", "tooltip-more-actions": "" @@ -2653,6 +2671,9 @@ "canvas": { "not-found-display": { "not-found": "" + }, + "text-display": { + "double-click-to-set": "" } }, "carousel": { @@ -2929,7 +2950,9 @@ }, "annotation-settings-list": { "aria-label-delete": "", + "built-in": "", "data-source": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3042,6 +3065,7 @@ } }, "confirm-plugin-dashboard-save-modal": { + "body-plugin-dashboard": "", "cancel": "", "overwrite": "", "title-plugin-dashboard": "" @@ -3064,6 +3088,7 @@ } }, "dashboard-validation": { + "body-dashboard-failed-schema-validation": "", "title-checking-dashboard-validity": "", "title-dashboard-failed-schema-validation": "", "title-error-checking-dashboard-validity": "" @@ -3198,6 +3223,7 @@ }, "gen-aihistory": { "aria-label-send-custom-feedback": "", + "footer-text": "", "placeholder-tell-ai-what-to-do-next": "" }, "get-field-override-categories": { @@ -3288,9 +3314,13 @@ "make-editable": "" }, "minimalistic-pagination": { + "page-count": "", "tooltip-next": "", "tooltip-previous": "" }, + "new-dashboard-with-ds": { + "not-found": "" + }, "new-panel": { "configure-button": "", "configure-button-menu": "", @@ -3352,9 +3382,12 @@ "go-back-to-queries": "" }, "public-dashboard-not-available": { + "does-not-exist": "", + "paused": "", "try-again-later": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, "row-options-button": { @@ -3416,6 +3449,8 @@ "description": "", "label-copy-tags": "", "label-folder": "", + "save": "", + "saving": "", "title": "" }, "save-dashboard-button": { @@ -3432,19 +3467,30 @@ "label-details": "" }, "save-dashboard-error-proxy": { - "title-conflict": "" + "body-name-exists": "", + "body-version-mismatch": "", + "title-name-exists": "", + "title-version-mismatch": "" }, "save-dashboard-form": { "cancel": "", "label-current-range-dashboard-default": "", "label-current-variable-values-dashboard-default": "", "no-changes-to-save": "", - "placeholder-describe-changes": "" + "placeholder-describe-changes": "", + "save": "", + "saving": "" }, "save-provisioned-dashboard-form": { "cancel": "", + "cannot-be-saved": "", "copy-json-to-clipboard": "", - "save-json-to-file": "" + "file-path": "", + "save-json-to-file": "", + "see-docs": "" + }, + "share-public-dashboard-loader": { + "loading-configuration": "" }, "solo-panel": { "loading-initializing-dashboard": "", @@ -3623,6 +3669,9 @@ "title-delete": "" }, "transformation-picker": { + "info": "", + "info-graph-not-suitable": "", + "info-switch-to-table": "", "placeholder-search-for-transformation": "", "read-more": "", "title-transformations": "" @@ -3647,6 +3696,9 @@ "discard": "", "title-unsaved-changes": "" }, + "untheme-dashboard-row": { + "dashboard-datasource": "" + }, "unthemed-dashboard-row": { "aria-label-delete-row": "", "learn-more": "" @@ -3658,7 +3710,10 @@ "tags-expected-strings": "tag array yang diharapkan dari string" }, "version-history-comparison": { - "label-view-json-diff": "" + "button-restore": "", + "label-view-json-diff": "", + "new-updated-by": "", + "old-updated-by": "" }, "version-history-table": { "aria-label-toggle-selection": "", @@ -3757,8 +3812,10 @@ "title-annotation-support-source": "" }, "annotation-settings-list": { + "built-in": "", "data-source": "", "delete-aria-label": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3819,6 +3876,8 @@ }, "data-source-variable-form": { "data-source-options": "", + "description-instance-name-filter": "", + "example-instance-name-filter": "", "selection-options": "" }, "description-label": { @@ -3879,7 +3938,12 @@ "title-plugin-dashboard": "", "title-someone-else-has-updated-this-dashboard": "", "would-still-dashboard": "" - } + }, + "save-and-overwrite": "" + }, + "library-viz-panel-info": { + "last-edited": "", + "usage-count_other": "" }, "name-already-exists-error": { "body-name-already-exists": "", @@ -3889,6 +3953,7 @@ "alert": { "title-errors-loading-rules": "" }, + "error-failed-to-load": "", "text-loading-rules": "", "title-dashboard-not-saved": "" }, @@ -3918,7 +3983,10 @@ "title-close": "" }, "provisioned-delete-modal": { + "cannot-be-deleted": "", + "file-path": "", "ok": "", + "see-docs": "", "title-cannot-delete-provisioned-dashboard": "" }, "provisioned-resource-delete-modal": { @@ -3931,13 +3999,21 @@ "query": "" }, "query-variable-editor-form": { + "description-examples": "", + "description-optional": "", "label-data-source": "", "query-options": "", "selection-options": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, + "save-button": { + "save": "", + "save-and-overwrite": "", + "saving": "" + }, "save-dashboard-as-form": { "aria-label-save-dashboard-description-field": "", "aria-label-save-dashboard-title-field": "", @@ -3990,17 +4066,20 @@ "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "affected-dashboards_other": "" }, "save-provisioned-dashboard-form": { "api-error": "Kesalahan saat menyimpan dasbor", "api-success": "Perubahan dasbor disimpan", "cancel": "Batalkan", + "cannot-be-saved": "", "copy-json-message": "Jika Anda memiliki akses langsung ke target, salin JSON dan tempel di sana.", "copy-json-to-clipboard": "", "dashboard-comment-placeholder-describe-changes-optional": "Tambahkan catatan untuk menjelaskan perubahan Anda (opsional)", "description-branch-name-in-git-hub": "Nama cabang di GitHub", "description-inside-repository": "Jalur file di dalam repositori (.json atau .yaml)", + "file-path": "", "label-branch": "Cabang", "label-comment": "Komentar", "label-description": "Deskripsi", @@ -4011,6 +4090,7 @@ "save": "Simpan", "save-json-to-file": "", "saving": "Menyimpan...", + "see-docs": "", "title-required": "Judul dasbor wajib diisi", "title-same-as-folder": "Nama dasbor tidak boleh sama dengan nama folder", "title-this-repository-is-read-only": "Repositori ini hanya dapat dibaca", @@ -4029,6 +4109,9 @@ "share-button": { "aria-label-sharedropdownmenu": "" }, + "solo-panel-page": { + "loading": "" + }, "text-box-variable-form": { "placeholder-default-value-if-any": "", "text-options": "" @@ -4088,10 +4171,18 @@ "preview-of-values": "", "show-more": "" }, + "version-history": { + "comparison": { + "button-restore": "" + } + }, "version-history-comparison": { - "label-view-json-diff": "" + "label-view-json-diff": "", + "new-version-updated": "", + "old-version-updated": "" }, "version-history-header": { + "compare-versions": "", "latest": "", "tooltip-reset-version": "" }, @@ -4267,6 +4358,7 @@ }, "button-row": { "delete": "", + "save-and-test": "", "test": "" }, "cloud-info-box": { @@ -4290,6 +4382,9 @@ "data-source-picker": { "aria-label-select-a-data-source": "" }, + "data-source-plugin-config-page": { + "page-not-found": "" + }, "data-source-plugin-state": { "plugin-state": "" }, @@ -4426,8 +4521,12 @@ } }, "explore": { + "accordian-logs": { + "footer": "" + }, "accordian-references": { - "references": "" + "references": "", + "view-linked-span": "" }, "add-to-dashboard": "Tambahkan ke dasbor", "basic-extensions": { @@ -4436,6 +4535,7 @@ "clear": "", "confirm-navigation-modal": { "cancel": "", + "new-tab": "", "open": "", "open-in-new-tab": "" }, @@ -4456,9 +4556,14 @@ "add-transformation": "", "aria-label-delete-transformation": "", "aria-label-edit-transformation": "", + "body-correlation-details": "", + "expression": "", "label-description": "", + "label-description-header": "", "label-label": "", - "title-correlation-details": "" + "title-correlation-details": "", + "tooltip-transformations": "", + "transformations": "" }, "correlation-transformation-add-modal": { "add-transformation": "", @@ -4484,10 +4589,13 @@ "title-unsaved-changes-to-correlation": "" }, "draggable-manager-demo": { + "click-and-drag-gray-divider": "", "click-horizontally-somewhere-colored-below": "", + "drag-value": "", "draggable-manager-demo": "", "dragging-a-divider": "", - "dragging-a-sub-region": "" + "dragging-a-sub-region": "", + "value": "" }, "drilldownInfo": { "action": "Buka Grafana Drilldown", @@ -4518,6 +4626,10 @@ "outline": "", "tooltip-content-outline": "" }, + "feature-toggle-page": { + "description-explore-disabled": "", + "title-explore-disabled": "" + }, "get-matches-metadata": { "matches": "" }, @@ -4528,10 +4640,15 @@ }, "live-logs": { "clear-logs": "", - "exit-live-mode": "" + "exit-live-mode": "", + "last-line-received": "", + "pause": "", + "resume": "" }, "live-tail-button": { + "live": "", "pause-the-live-stream": "", + "paused": "", "start-live-stream-your-logs": "", "stop-and-exit-the-live-stream": "" }, @@ -4560,12 +4677,14 @@ "show-original-line": "" }, "logs-sample-panel": { + "label": "", "logs-sample-is-loading": "", "no-logs-sample-data": "", "open-in-split-view-button": { "open-logs-in-split-view": "" }, - "title-failed-sample-query": "" + "title-failed-sample-query": "", + "tooltip": "" }, "logs-table-available-fields": { "title-label-percentage": "" @@ -4574,7 +4693,9 @@ "no-fields": "" }, "logs-table-multi-select": { - "fields": "" + "fields": "", + "reset": "", + "selected-fields": "" }, "logs-table-nav-field": { "aria-label-drag-and-drop-icon": "", @@ -4593,13 +4714,20 @@ "title-showing-partial-data": "", "title-unable-to-show-log-volume": "" }, + "logs-volumne-panel-list": { + "body-no-logs-volume-available": "" + }, "make-datasource-setup": { "aria-label-query": "" }, "next": "", "next-prev-result": { "aria-label-next": "", - "aria-label-prev": "" + "aria-label-prev": "", + "depth": "", + "depth-span-filter-matches": "", + "services": "", + "services-span-filter-matches": "" }, "no-data-source-call-to-action": { "cta-element": { @@ -4615,6 +4743,7 @@ "aria-label-go-queryless": "" }, "raw-list-container": { + "item-count": "", "label-expand-results": "" }, "raw-list-item": { @@ -4630,7 +4759,6 @@ "newest-first": "Terbaru lebih dulu", "oldest-first": "Terlama lebih dulu", "query-history": "Riwayat kueri", - "query-library": "Pustaka kueri", "settings": "Pengaturan", "starred": "Dibintangi" }, @@ -4746,6 +4874,9 @@ }, "show-critical-path-only": "", "show-matches-only": "", + "span-bar": { + "tooltip-critical-path": "" + }, "span-bar-section": { "description-link": "", "title-span-bar": "" @@ -4772,6 +4903,7 @@ "aria-label-select-service-name-operator": "", "aria-label-select-span-name": "", "aria-label-select-span-name-operator": "", + "label-collapse": "", "label-duration": "", "label-service-name": "", "label-span-name": "", @@ -4815,6 +4947,9 @@ "tooltip-expand": "", "tooltip-expand-all": "" }, + "timeline-header-row": { + "title": "" + }, "toolbar": { "add-to-extensions": "Tambahkan", "add-to-queryless-extensions": "Buka tanpa kueri", @@ -4844,7 +4979,8 @@ "title-share-thoughts-about-tracing-grafana": "" }, "trace-page-header": { - "text-partial-trace": "" + "text-partial-trace": "", + "tooltip-url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "", @@ -4858,6 +4994,7 @@ "title-trace": "" }, "unconnected-node-graph-container": { + "count-warning": "", "title-node-graph": "" }, "unthemed-logs": { @@ -4911,11 +5048,20 @@ "classic-conditions": { "label-conditions": "" }, + "condition": { + "of": "", + "to": "", + "when": "" + }, "expression-query-editor": { "label-operation": "" }, "math": { - "available-math-functions": "" + "available-math-functions": "", + "run-math-operations": "", + "tooltip-footer": "", + "tooltip-title": "", + "tooltip-trigger": "" }, "reduce": { "label-function": "", @@ -4994,6 +5140,12 @@ }, "essentials": { "title-essentials": "" + }, + "progress-bar": { + "steps-status": "" + }, + "progress-status": { + "your-progress": "" } }, "grafana": { @@ -5385,8 +5537,14 @@ }, "inspector": { "inspect-data-tab": { + "loading": "", "no-data": "" }, + "inspect-error-tab": { + "error-status-message": "", + "error-status-no-message": "", + "error-trace-message": "" + }, "inspect-jsontab": { "apply": "", "support": "" @@ -5401,7 +5559,9 @@ }, "query-inspector": { "query-inspector": "", - "text-loading-query-inspector": "" + "text-loading-query-inspector": "", + "count-frames_other": "", + "count-rows_other": "" } }, "invites": { @@ -5413,6 +5573,11 @@ "name": "" }, "signup-invited-page": { + "complete-following": "", + "custom-has-invited-you": "", + "default-has-invited-you": "", + "greeting-custom": "", + "greeting-default": "", "label-email": "", "label-name": "", "label-password": "", @@ -5608,6 +5773,10 @@ "has-connected-dashboards": { "dashboard-name": "" }, + "library-panel-info": { + "last-edited": "", + "usage-count_other": "" + }, "library-panels-search": { "placeholder-search-by-name-or-description": "" }, @@ -5633,12 +5802,14 @@ "success": "Panel pustaka disimpan" }, "save-library-panel-modal": { + "affected-dashboards": "", "cancel": "", "dashboard-name": "", "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "num-affected_other": "" } }, "link": { @@ -5663,6 +5834,7 @@ "live": { "dashboard-changed-modal": { "continue-editing": "", + "description": "", "discard-local-changes": "", "title-dashboard-changed": "" }, @@ -5741,6 +5913,9 @@ "collapse": "", "expand": "" }, + "log-labels-list": { + "log-line": "" + }, "log-line-menu": { "copy-link": "Salin tautan ke baris log", "copy-log": "Salin baris log", @@ -5830,6 +6005,8 @@ "aria-label-no-details": "" }, "un-themed-log-details-row": { + "filter-out": "", + "filter-out-query": "", "title-copy-value-to-clipboard": "", "toggle-field-button": { "tooltip-field-instead-message": "", @@ -5837,6 +6014,10 @@ }, "tooltip-adhoc-statistics": "", "tooltip-hide-adhoc-statistics": "" + }, + "un-themed-log-label-stats": { + "field-log-stats": "", + "label-log-stats": "" } }, "manage-dashbaords": { @@ -5858,6 +6039,7 @@ "options": "" }, "import-dashboard-overview-un-connected": { + "importing-from": "", "published-by": "", "updated-on": "" }, @@ -5917,6 +6099,9 @@ "button": "Migrasikan instans ini ke Cloud", "header": "Biarkan kami mengelola tumpukan Grafana Anda" }, + "dashboard-info": { + "dashboard": "" + }, "delete-migration-token-confirm": { "body": "Jika Anda sudah menggunakan token ini dengan instalasi yang dikelola sendiri, instalasi itu tidak akan dapat lagi mengunggah konten.", "confirm-button": "Hapus token", @@ -5932,6 +6117,7 @@ "title": "Putuskan koneksi dari tumpukan cloud" }, "folder-info": { + "folder": "", "unable-to-load-folder": "" }, "get-started": { @@ -6554,6 +6740,7 @@ "org": { "new-org-page": { "create": "", + "description": "", "label-organization-name": "", "placeholder-org-name": "" }, @@ -6562,6 +6749,9 @@ "label-organization-profile": "", "update-organization-name": "" }, + "select-org-page": { + "description": "" + }, "user-invite-form": { "back": "", "label-email-or-username": "", @@ -6569,7 +6759,11 @@ "label-send-invite-email": "", "placeholder-optional": "", "role": "", - "submit": "" + "submit": "", + "tooltip": "" + }, + "user-invite-page": { + "sub-title": "" } }, "org-picker": { @@ -6612,7 +6806,9 @@ "title-not-found": "" }, "panel-renderer": { + "failed-to-load-plugin": "", "loading-plugin-panel": "", + "no-panel-component": "", "no-panel-data": "" }, "panel-type-card": { @@ -6639,7 +6835,14 @@ "select-placeholder": "" }, "playlist": { + "playlist-table-rows": { + "aria-label-playlist-item": "", + "multiple-dashboards-found": "", + "no-dashboards-found": "", + "not-found": "" + }, "start-modal": { + "button-start": "", "description-customize-dashboard-elements-visibility": "", "description-panel-heights-adjusted-screen": "", "label-autofit": "", @@ -6699,6 +6902,11 @@ "message": "Daftar putar tidak ditemukan" } }, + "plugin": { + "plugin-details-deprecated-warning": { + "body-deprecated": "" + } + }, "plugins": { "app-root-page": { "access-denied": { @@ -6802,6 +7010,12 @@ "add-new-data-source": "", "title-button-disabled": "" }, + "install-controls": { + "install": "", + "installing": "", + "update": "", + "updating": "" + }, "install-controls-button": { "title-uninstall-modal": "" }, @@ -6820,6 +7034,7 @@ "label-severity": "" }, "not-found-plugin": { + "body-plugin-not-found": "", "title-plugin-not-found": "" }, "plugin-actions": { @@ -6834,12 +7049,17 @@ "tooltip-plugin-deprecated-longer-receives-updates": "" }, "plugin-details-body": { + "needs-service-account": "", "page-not-found": "" }, "plugin-details-deprecated-warning": { "title-deprecated": "" }, + "plugin-details-header-dependencies": { + "grafana-dependency": "" + }, "plugin-details-signature": { + "body-invalid-plugin-signature": "", "read-more-about-plugins-signing": "", "title-invalid-plugin-signature": "" }, @@ -6870,6 +7090,8 @@ "update-available": "" }, "plugin-usage": { + "body-missing-feature-toggle-panel-title-search": "", + "num-usages": "", "title-missing-feature-toggle-panel-title-search": "", "title-not-used-yet": "" }, @@ -6886,7 +7108,9 @@ }, "version-list": { "grafana-dependency": "", + "installed-version": "", "last-updated": "", + "latest-compatible-version": "", "no-version-history-was-found": "", "version": "" } @@ -6909,6 +7133,12 @@ }, "change-theme": "Ubah tema", "enable-kiosk-mode": "", + "feature-toggle-page": { + "profile-not-enabled": "" + }, + "feature-toggles-age": { + "enable-in-config": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -7052,6 +7282,8 @@ "repository-config-exists-configuration": "" }, "file-status-page": { + "save": "", + "saving": "", "title-error-loading-file": "" }, "files-view": { @@ -7082,7 +7314,6 @@ }, "getting-started": { "alert-temporary-outage": "", - "engaging-graphic": "", "modal-description-public-access": "", "modal-description-required-features": "", "modal-title-set-up-public-access": "", @@ -7178,13 +7409,16 @@ "checked": "", "finished": "", "health": "", + "healthy": "", "job-id": "", "last-ref": "", "messages": "", + "not-available": "", "pull-status": "", "resources": "", "started": "", "status": "", + "unhealthy": "", "view-folder": "", "webhook": "", "webhook-events": "", @@ -7474,31 +7708,41 @@ "hidden": "", "query-name-div-title-edit-query-name": "" }, + "query-error-alert": { + "trace-id": "" + }, "query-group": { "add-query": "", "expression": "", "title-data-source-help": "" }, "query-group-options-editor": { + "collapsed-interval": "", + "collapsed-max-data-points": "", "hide-time-info": "", "Query options-title-query-options": "", "relative-time": "", + "relative-time-tooltip": "", "render-cache-timeout-option": { "cache-timeout": "" }, "render-interval-option": { "interval": "", + "interval-tooltip": "", "min-interval": "", + "min-interval-tooltip": "", "time-range-max-data-points": "" }, "render-max-data-points-option": { "max-data-points": "", + "max-data-points-tooltip": "", "width-of-panel": "" }, "render-query-caching-ttloption": { "cache-ttl": "" }, - "time-shift": "" + "time-shift": "", + "time-shift-tooltip": "" }, "query-group-top-section": { "data-source": "", @@ -7677,6 +7921,9 @@ } }, "search": { + "action-row": { + "panel-type": "" + }, "actions": { "include-panels": "Sertakan panel", "remove-datasource-filter": "Sumber data: {{datasource}}", @@ -7800,6 +8047,9 @@ "label-roles": "", "used-by": "" }, + "service-account-profile-row": { + "edit": "" + }, "service-account-role-row": { "aria-label-role": "" }, @@ -8324,6 +8574,7 @@ "generate-enum-values-from-data": "" }, "enum-mapping-row": { + "click-to-edit": "", "remove-enum-row-aria-label-delete-enum-row": "", "remove-enum-row-tooltip-delete": "" }, @@ -8378,10 +8629,13 @@ "label-format": "", "label-set-timezone": "", "label-time-field": "", + "tooltip-format": "", "tooltip-timezone-manually": "" }, "get-tooltips": { - "json-value": "" + "description": "", + "json-value": "", + "valid-paths": "" }, "group-by-field-configuration": { "placeholder-ignored": "", @@ -8598,6 +8852,7 @@ "message": "Pengguna tidak ditemukan" }, "token-revoked-modal": { + "auto-revoked": "", "resume-message": "", "sign-in": "", "title-you-have-been-automatically-signed-out": "" @@ -8692,7 +8947,8 @@ "general": "", "placeholder-descriptive-text": "", "placeholder-label-name": "", - "placeholder-variable-name": "" + "placeholder-variable-name": "", + "run-query": "" }, "variable-editor-list": { "definition": "", diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index 1115ce2a745..28096ddddc2 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -1,6 +1,5 @@ { "_comment": "Il codice rappresenta l'unica fonte di informazioni affidabile per le frasi in inglese. Queste ultime devono essere aggiornate direttamente nei componenti e i plurali aggiuntivi devono essere specificati in questo file.", - "{{pageStart}} - {{pageEnd}} of {{numPages}}": "", "access-control": { "add-permission": { "built-in-aria-label": "", @@ -969,7 +968,8 @@ "expression-result": { "aria-label-nextpage": "", "aria-label-previouspage": "", - "no-data": "" + "no-data": "", + "page-counter": "" }, "federated-rule-warning": { "experimental": "", @@ -2004,6 +2004,10 @@ "title": "Regole di avviso" }, "rulerrule-loading-error": "Impossibile caricare la regola", + "toggle": { + "go-back-to-old-look": "", + "try-out-the-new-look": "" + }, "unknown-rule-type": "" }, "rule-list-errors": { @@ -2513,6 +2517,18 @@ "configuration-required": "", "refer-documentation-configure-authentication": "" }, + "fields": { + "allowed-groups-description": "", + "allowed-groups-description-oauth": "", + "api-url-description": "", + "team-ids-description": "", + "team-ids-description-oauth": "", + "team-ids-github": "", + "team-ids-other": "", + "teams-url-description": "", + "teams-url-description-oauth": "", + "use-pkce-description": "" + }, "provider-card": { "text-badge-enabled": "", "text-badge-not-enabled": "" @@ -2526,6 +2542,8 @@ "label-enabled": "", "reset-configuration": "", "reset-configuration-description": "", + "save": "", + "saving": "", "title-more-actions": "", "title-reset": "", "tooltip-more-actions": "" @@ -2671,6 +2689,9 @@ "canvas": { "not-found-display": { "not-found": "" + }, + "text-display": { + "double-click-to-set": "" } }, "carousel": { @@ -2947,7 +2968,9 @@ }, "annotation-settings-list": { "aria-label-delete": "", + "built-in": "", "data-source": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3060,6 +3083,7 @@ } }, "confirm-plugin-dashboard-save-modal": { + "body-plugin-dashboard": "", "cancel": "", "overwrite": "", "title-plugin-dashboard": "" @@ -3082,6 +3106,7 @@ } }, "dashboard-validation": { + "body-dashboard-failed-schema-validation": "", "title-checking-dashboard-validity": "", "title-dashboard-failed-schema-validation": "", "title-error-checking-dashboard-validity": "" @@ -3216,6 +3241,7 @@ }, "gen-aihistory": { "aria-label-send-custom-feedback": "", + "footer-text": "", "placeholder-tell-ai-what-to-do-next": "" }, "get-field-override-categories": { @@ -3306,9 +3332,13 @@ "make-editable": "" }, "minimalistic-pagination": { + "page-count": "", "tooltip-next": "", "tooltip-previous": "" }, + "new-dashboard-with-ds": { + "not-found": "" + }, "new-panel": { "configure-button": "", "configure-button-menu": "", @@ -3371,9 +3401,12 @@ "go-back-to-queries": "" }, "public-dashboard-not-available": { + "does-not-exist": "", + "paused": "", "try-again-later": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, "row-options-button": { @@ -3435,6 +3468,8 @@ "description": "", "label-copy-tags": "", "label-folder": "", + "save": "", + "saving": "", "title": "" }, "save-dashboard-button": { @@ -3451,19 +3486,30 @@ "label-details": "" }, "save-dashboard-error-proxy": { - "title-conflict": "" + "body-name-exists": "", + "body-version-mismatch": "", + "title-name-exists": "", + "title-version-mismatch": "" }, "save-dashboard-form": { "cancel": "", "label-current-range-dashboard-default": "", "label-current-variable-values-dashboard-default": "", "no-changes-to-save": "", - "placeholder-describe-changes": "" + "placeholder-describe-changes": "", + "save": "", + "saving": "" }, "save-provisioned-dashboard-form": { "cancel": "", + "cannot-be-saved": "", "copy-json-to-clipboard": "", - "save-json-to-file": "" + "file-path": "", + "save-json-to-file": "", + "see-docs": "" + }, + "share-public-dashboard-loader": { + "loading-configuration": "" }, "solo-panel": { "loading-initializing-dashboard": "", @@ -3642,6 +3688,9 @@ "title-delete": "" }, "transformation-picker": { + "info": "", + "info-graph-not-suitable": "", + "info-switch-to-table": "", "placeholder-search-for-transformation": "", "read-more": "", "title-transformations": "" @@ -3666,6 +3715,9 @@ "discard": "", "title-unsaved-changes": "" }, + "untheme-dashboard-row": { + "dashboard-datasource": "" + }, "unthemed-dashboard-row": { "aria-label-delete-row": "", "learn-more": "" @@ -3677,7 +3729,10 @@ "tags-expected-strings": "array di stringhe con tag previsti" }, "version-history-comparison": { - "label-view-json-diff": "" + "button-restore": "", + "label-view-json-diff": "", + "new-updated-by": "", + "old-updated-by": "" }, "version-history-table": { "aria-label-toggle-selection": "", @@ -3776,8 +3831,10 @@ "title-annotation-support-source": "" }, "annotation-settings-list": { + "built-in": "", "data-source": "", "delete-aria-label": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3838,6 +3895,8 @@ }, "data-source-variable-form": { "data-source-options": "", + "description-instance-name-filter": "", + "example-instance-name-filter": "", "selection-options": "" }, "description-label": { @@ -3898,7 +3957,13 @@ "title-plugin-dashboard": "", "title-someone-else-has-updated-this-dashboard": "", "would-still-dashboard": "" - } + }, + "save-and-overwrite": "" + }, + "library-viz-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_other": "" }, "name-already-exists-error": { "body-name-already-exists": "", @@ -3908,6 +3973,7 @@ "alert": { "title-errors-loading-rules": "" }, + "error-failed-to-load": "", "text-loading-rules": "", "title-dashboard-not-saved": "" }, @@ -3937,7 +4003,10 @@ "title-close": "" }, "provisioned-delete-modal": { + "cannot-be-deleted": "", + "file-path": "", "ok": "", + "see-docs": "", "title-cannot-delete-provisioned-dashboard": "" }, "provisioned-resource-delete-modal": { @@ -3950,13 +4019,21 @@ "query": "" }, "query-variable-editor-form": { + "description-examples": "", + "description-optional": "", "label-data-source": "", "query-options": "", "selection-options": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, + "save-button": { + "save": "", + "save-and-overwrite": "", + "saving": "" + }, "save-dashboard-as-form": { "aria-label-save-dashboard-description-field": "", "aria-label-save-dashboard-title-field": "", @@ -4009,17 +4086,21 @@ "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "affected-dashboards_one": "", + "affected-dashboards_other": "" }, "save-provisioned-dashboard-form": { "api-error": "Errore durante il salvataggio del dashboard", "api-success": "Modifiche al dashboard salvate", "cancel": "Annulla", + "cannot-be-saved": "", "copy-json-message": "Se hai accesso diretto alla destinazione, copia il file JSON e incollalo lì.", "copy-json-to-clipboard": "", "dashboard-comment-placeholder-describe-changes-optional": "Aggiungi una nota per descrivere le modifiche (facoltativo)", "description-branch-name-in-git-hub": "Nome del ramo in GitHub", "description-inside-repository": "Percorso del file all'interno del repository (.json o .yaml)", + "file-path": "", "label-branch": "Ramo", "label-comment": "Commento", "label-description": "Descrizione", @@ -4030,6 +4111,7 @@ "save": "Salva", "save-json-to-file": "", "saving": "Salvataggio in corso...", + "see-docs": "", "title-required": "Il titolo del dashboard è obbligatorio", "title-same-as-folder": "Il nome del dashboard non può essere uguale al nome della cartella", "title-this-repository-is-read-only": "Questo repository è di sola lettura", @@ -4048,6 +4130,9 @@ "share-button": { "aria-label-sharedropdownmenu": "" }, + "solo-panel-page": { + "loading": "" + }, "text-box-variable-form": { "placeholder-default-value-if-any": "", "text-options": "" @@ -4107,10 +4192,18 @@ "preview-of-values": "", "show-more": "" }, + "version-history": { + "comparison": { + "button-restore": "" + } + }, "version-history-comparison": { - "label-view-json-diff": "" + "label-view-json-diff": "", + "new-version-updated": "", + "old-version-updated": "" }, "version-history-header": { + "compare-versions": "", "latest": "", "tooltip-reset-version": "" }, @@ -4286,6 +4379,7 @@ }, "button-row": { "delete": "", + "save-and-test": "", "test": "" }, "cloud-info-box": { @@ -4309,6 +4403,9 @@ "data-source-picker": { "aria-label-select-a-data-source": "" }, + "data-source-plugin-config-page": { + "page-not-found": "" + }, "data-source-plugin-state": { "plugin-state": "" }, @@ -4445,8 +4542,12 @@ } }, "explore": { + "accordian-logs": { + "footer": "" + }, "accordian-references": { - "references": "" + "references": "", + "view-linked-span": "" }, "add-to-dashboard": "Aggiungi al dashboard", "basic-extensions": { @@ -4455,6 +4556,7 @@ "clear": "", "confirm-navigation-modal": { "cancel": "", + "new-tab": "", "open": "", "open-in-new-tab": "" }, @@ -4475,9 +4577,14 @@ "add-transformation": "", "aria-label-delete-transformation": "", "aria-label-edit-transformation": "", + "body-correlation-details": "", + "expression": "", "label-description": "", + "label-description-header": "", "label-label": "", - "title-correlation-details": "" + "title-correlation-details": "", + "tooltip-transformations": "", + "transformations": "" }, "correlation-transformation-add-modal": { "add-transformation": "", @@ -4503,10 +4610,13 @@ "title-unsaved-changes-to-correlation": "" }, "draggable-manager-demo": { + "click-and-drag-gray-divider": "", "click-horizontally-somewhere-colored-below": "", + "drag-value": "", "draggable-manager-demo": "", "dragging-a-divider": "", - "dragging-a-sub-region": "" + "dragging-a-sub-region": "", + "value": "" }, "drilldownInfo": { "action": "Vai a Grafana Drilldown", @@ -4537,6 +4647,10 @@ "outline": "", "tooltip-content-outline": "" }, + "feature-toggle-page": { + "description-explore-disabled": "", + "title-explore-disabled": "" + }, "get-matches-metadata": { "matches": "" }, @@ -4547,10 +4661,15 @@ }, "live-logs": { "clear-logs": "", - "exit-live-mode": "" + "exit-live-mode": "", + "last-line-received": "", + "pause": "", + "resume": "" }, "live-tail-button": { + "live": "", "pause-the-live-stream": "", + "paused": "", "start-live-stream-your-logs": "", "stop-and-exit-the-live-stream": "" }, @@ -4579,12 +4698,14 @@ "show-original-line": "" }, "logs-sample-panel": { + "label": "", "logs-sample-is-loading": "", "no-logs-sample-data": "", "open-in-split-view-button": { "open-logs-in-split-view": "" }, - "title-failed-sample-query": "" + "title-failed-sample-query": "", + "tooltip": "" }, "logs-table-available-fields": { "title-label-percentage": "" @@ -4593,7 +4714,9 @@ "no-fields": "" }, "logs-table-multi-select": { - "fields": "" + "fields": "", + "reset": "", + "selected-fields": "" }, "logs-table-nav-field": { "aria-label-drag-and-drop-icon": "", @@ -4612,13 +4735,20 @@ "title-showing-partial-data": "", "title-unable-to-show-log-volume": "" }, + "logs-volumne-panel-list": { + "body-no-logs-volume-available": "" + }, "make-datasource-setup": { "aria-label-query": "" }, "next": "", "next-prev-result": { "aria-label-next": "", - "aria-label-prev": "" + "aria-label-prev": "", + "depth": "", + "depth-span-filter-matches": "", + "services": "", + "services-span-filter-matches": "" }, "no-data-source-call-to-action": { "cta-element": { @@ -4634,6 +4764,7 @@ "aria-label-go-queryless": "" }, "raw-list-container": { + "item-count": "", "label-expand-results": "" }, "raw-list-item": { @@ -4649,7 +4780,6 @@ "newest-first": "Prima il più recente", "oldest-first": "Prima il meno recente", "query-history": "Cronologia query", - "query-library": "Libreria query", "settings": "Impostazioni", "starred": "Preferito" }, @@ -4765,6 +4895,9 @@ }, "show-critical-path-only": "", "show-matches-only": "", + "span-bar": { + "tooltip-critical-path": "" + }, "span-bar-section": { "description-link": "", "title-span-bar": "" @@ -4791,6 +4924,7 @@ "aria-label-select-service-name-operator": "", "aria-label-select-span-name": "", "aria-label-select-span-name-operator": "", + "label-collapse": "", "label-duration": "", "label-service-name": "", "label-span-name": "", @@ -4834,6 +4968,9 @@ "tooltip-expand": "", "tooltip-expand-all": "" }, + "timeline-header-row": { + "title": "" + }, "toolbar": { "add-to-extensions": "Aggiungi", "add-to-queryless-extensions": "Passa a senza query", @@ -4863,7 +5000,8 @@ "title-share-thoughts-about-tracing-grafana": "" }, "trace-page-header": { - "text-partial-trace": "" + "text-partial-trace": "", + "tooltip-url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "", @@ -4877,6 +5015,7 @@ "title-trace": "" }, "unconnected-node-graph-container": { + "count-warning": "", "title-node-graph": "" }, "unthemed-logs": { @@ -4930,11 +5069,20 @@ "classic-conditions": { "label-conditions": "" }, + "condition": { + "of": "", + "to": "", + "when": "" + }, "expression-query-editor": { "label-operation": "" }, "math": { - "available-math-functions": "" + "available-math-functions": "", + "run-math-operations": "", + "tooltip-footer": "", + "tooltip-title": "", + "tooltip-trigger": "" }, "reduce": { "label-function": "", @@ -5013,6 +5161,12 @@ }, "essentials": { "title-essentials": "" + }, + "progress-bar": { + "steps-status": "" + }, + "progress-status": { + "your-progress": "" } }, "grafana": { @@ -5404,8 +5558,14 @@ }, "inspector": { "inspect-data-tab": { + "loading": "", "no-data": "" }, + "inspect-error-tab": { + "error-status-message": "", + "error-status-no-message": "", + "error-trace-message": "" + }, "inspect-jsontab": { "apply": "", "support": "" @@ -5420,7 +5580,11 @@ }, "query-inspector": { "query-inspector": "", - "text-loading-query-inspector": "" + "text-loading-query-inspector": "", + "count-frames_one": "", + "count-frames_other": "", + "count-rows_one": "", + "count-rows_other": "" } }, "invites": { @@ -5432,6 +5596,11 @@ "name": "" }, "signup-invited-page": { + "complete-following": "", + "custom-has-invited-you": "", + "default-has-invited-you": "", + "greeting-custom": "", + "greeting-default": "", "label-email": "", "label-name": "", "label-password": "", @@ -5627,6 +5796,11 @@ "has-connected-dashboards": { "dashboard-name": "" }, + "library-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_other": "" + }, "library-panels-search": { "placeholder-search-by-name-or-description": "" }, @@ -5653,12 +5827,15 @@ "success": "Pannello della libreria salvato" }, "save-library-panel-modal": { + "affected-dashboards": "", "cancel": "", "dashboard-name": "", "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "num-affected_one": "", + "num-affected_other": "" } }, "link": { @@ -5683,6 +5860,7 @@ "live": { "dashboard-changed-modal": { "continue-editing": "", + "description": "", "discard-local-changes": "", "title-dashboard-changed": "" }, @@ -5761,6 +5939,9 @@ "collapse": "", "expand": "" }, + "log-labels-list": { + "log-line": "" + }, "log-line-menu": { "copy-link": "Copia il link alla riga del registro", "copy-log": "Copia la riga del registro", @@ -5850,6 +6031,8 @@ "aria-label-no-details": "" }, "un-themed-log-details-row": { + "filter-out": "", + "filter-out-query": "", "title-copy-value-to-clipboard": "", "toggle-field-button": { "tooltip-field-instead-message": "", @@ -5857,6 +6040,10 @@ }, "tooltip-adhoc-statistics": "", "tooltip-hide-adhoc-statistics": "" + }, + "un-themed-log-label-stats": { + "field-log-stats": "", + "label-log-stats": "" } }, "manage-dashbaords": { @@ -5878,6 +6065,7 @@ "options": "" }, "import-dashboard-overview-un-connected": { + "importing-from": "", "published-by": "", "updated-on": "" }, @@ -5937,6 +6125,9 @@ "button": "Esegui la migrazione di questa istanza su Cloud", "header": "Permettici di gestire il tuo stack Grafana" }, + "dashboard-info": { + "dashboard": "" + }, "delete-migration-token-confirm": { "body": "Se hai già utilizzato questo token con un'installazione autogestita, tale installazione non sarà più in grado di caricare contenuti.", "confirm-button": "Elimina token", @@ -5952,6 +6143,7 @@ "title": "Disconnetti dallo stack cloud" }, "folder-info": { + "folder": "", "unable-to-load-folder": "" }, "get-started": { @@ -6574,6 +6766,7 @@ "org": { "new-org-page": { "create": "", + "description": "", "label-organization-name": "", "placeholder-org-name": "" }, @@ -6582,6 +6775,9 @@ "label-organization-profile": "", "update-organization-name": "" }, + "select-org-page": { + "description": "" + }, "user-invite-form": { "back": "", "label-email-or-username": "", @@ -6589,7 +6785,11 @@ "label-send-invite-email": "", "placeholder-optional": "", "role": "", - "submit": "" + "submit": "", + "tooltip": "" + }, + "user-invite-page": { + "sub-title": "" } }, "org-picker": { @@ -6632,7 +6832,9 @@ "title-not-found": "" }, "panel-renderer": { + "failed-to-load-plugin": "", "loading-plugin-panel": "", + "no-panel-component": "", "no-panel-data": "" }, "panel-type-card": { @@ -6659,7 +6861,14 @@ "select-placeholder": "" }, "playlist": { + "playlist-table-rows": { + "aria-label-playlist-item": "", + "multiple-dashboards-found": "", + "no-dashboards-found": "", + "not-found": "" + }, "start-modal": { + "button-start": "", "description-customize-dashboard-elements-visibility": "", "description-panel-heights-adjusted-screen": "", "label-autofit": "", @@ -6719,6 +6928,11 @@ "message": "Non sono state trovate playlist" } }, + "plugin": { + "plugin-details-deprecated-warning": { + "body-deprecated": "" + } + }, "plugins": { "app-root-page": { "access-denied": { @@ -6822,6 +7036,12 @@ "add-new-data-source": "", "title-button-disabled": "" }, + "install-controls": { + "install": "", + "installing": "", + "update": "", + "updating": "" + }, "install-controls-button": { "title-uninstall-modal": "" }, @@ -6840,6 +7060,7 @@ "label-severity": "" }, "not-found-plugin": { + "body-plugin-not-found": "", "title-plugin-not-found": "" }, "plugin-actions": { @@ -6854,12 +7075,17 @@ "tooltip-plugin-deprecated-longer-receives-updates": "" }, "plugin-details-body": { + "needs-service-account": "", "page-not-found": "" }, "plugin-details-deprecated-warning": { "title-deprecated": "" }, + "plugin-details-header-dependencies": { + "grafana-dependency": "" + }, "plugin-details-signature": { + "body-invalid-plugin-signature": "", "read-more-about-plugins-signing": "", "title-invalid-plugin-signature": "" }, @@ -6890,6 +7116,8 @@ "update-available": "" }, "plugin-usage": { + "body-missing-feature-toggle-panel-title-search": "", + "num-usages": "", "title-missing-feature-toggle-panel-title-search": "", "title-not-used-yet": "" }, @@ -6906,7 +7134,9 @@ }, "version-list": { "grafana-dependency": "", + "installed-version": "", "last-updated": "", + "latest-compatible-version": "", "no-version-history-was-found": "", "version": "" } @@ -6929,6 +7159,12 @@ }, "change-theme": "Modifica tema ", "enable-kiosk-mode": "", + "feature-toggle-page": { + "profile-not-enabled": "" + }, + "feature-toggles-age": { + "enable-in-config": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -7073,6 +7309,8 @@ "repository-config-exists-configuration": "" }, "file-status-page": { + "save": "", + "saving": "", "title-error-loading-file": "" }, "files-view": { @@ -7103,7 +7341,6 @@ }, "getting-started": { "alert-temporary-outage": "", - "engaging-graphic": "", "modal-description-public-access": "", "modal-description-required-features": "", "modal-title-set-up-public-access": "", @@ -7199,13 +7436,16 @@ "checked": "", "finished": "", "health": "", + "healthy": "", "job-id": "", "last-ref": "", "messages": "", + "not-available": "", "pull-status": "", "resources": "", "started": "", "status": "", + "unhealthy": "", "view-folder": "", "webhook": "", "webhook-events": "", @@ -7496,31 +7736,41 @@ "hidden": "", "query-name-div-title-edit-query-name": "" }, + "query-error-alert": { + "trace-id": "" + }, "query-group": { "add-query": "", "expression": "", "title-data-source-help": "" }, "query-group-options-editor": { + "collapsed-interval": "", + "collapsed-max-data-points": "", "hide-time-info": "", "Query options-title-query-options": "", "relative-time": "", + "relative-time-tooltip": "", "render-cache-timeout-option": { "cache-timeout": "" }, "render-interval-option": { "interval": "", + "interval-tooltip": "", "min-interval": "", + "min-interval-tooltip": "", "time-range-max-data-points": "" }, "render-max-data-points-option": { "max-data-points": "", + "max-data-points-tooltip": "", "width-of-panel": "" }, "render-query-caching-ttloption": { "cache-ttl": "" }, - "time-shift": "" + "time-shift": "", + "time-shift-tooltip": "" }, "query-group-top-section": { "data-source": "", @@ -7702,6 +7952,9 @@ } }, "search": { + "action-row": { + "panel-type": "" + }, "actions": { "include-panels": "Includi pannelli", "remove-datasource-filter": "Origine dati: {{datasource}}", @@ -7825,6 +8078,9 @@ "label-roles": "", "used-by": "" }, + "service-account-profile-row": { + "edit": "" + }, "service-account-role-row": { "aria-label-role": "" }, @@ -8349,6 +8605,7 @@ "generate-enum-values-from-data": "" }, "enum-mapping-row": { + "click-to-edit": "", "remove-enum-row-aria-label-delete-enum-row": "", "remove-enum-row-tooltip-delete": "" }, @@ -8403,10 +8660,13 @@ "label-format": "", "label-set-timezone": "", "label-time-field": "", + "tooltip-format": "", "tooltip-timezone-manually": "" }, "get-tooltips": { - "json-value": "" + "description": "", + "json-value": "", + "valid-paths": "" }, "group-by-field-configuration": { "placeholder-ignored": "", @@ -8623,6 +8883,7 @@ "message": "Nessun utente trovato" }, "token-revoked-modal": { + "auto-revoked": "", "resume-message": "", "sign-in": "", "title-you-have-been-automatically-signed-out": "" @@ -8717,7 +8978,8 @@ "general": "", "placeholder-descriptive-text": "", "placeholder-label-name": "", - "placeholder-variable-name": "" + "placeholder-variable-name": "", + "run-query": "" }, "variable-editor-list": { "definition": "", diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index 7047aa89e81..1f41f360e3d 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -1,6 +1,5 @@ { "_comment": "コードが英語フレーズの信頼できる情報源です。コンポーネント内で直接更新され、このファイルで指定された複数形が追加されます。", - "{{pageStart}} - {{pageEnd}} of {{numPages}}": "", "access-control": { "add-permission": { "built-in-aria-label": "", @@ -963,7 +962,8 @@ "expression-result": { "aria-label-nextpage": "", "aria-label-previouspage": "", - "no-data": "" + "no-data": "", + "page-counter": "" }, "federated-rule-warning": { "experimental": "", @@ -1994,6 +1994,10 @@ "title": "アラートルール" }, "rulerrule-loading-error": "ルールの読み込みに失敗しました", + "toggle": { + "go-back-to-old-look": "", + "try-out-the-new-look": "" + }, "unknown-rule-type": "" }, "rule-list-errors": { @@ -2500,6 +2504,18 @@ "configuration-required": "", "refer-documentation-configure-authentication": "" }, + "fields": { + "allowed-groups-description": "", + "allowed-groups-description-oauth": "", + "api-url-description": "", + "team-ids-description": "", + "team-ids-description-oauth": "", + "team-ids-github": "", + "team-ids-other": "", + "teams-url-description": "", + "teams-url-description-oauth": "", + "use-pkce-description": "" + }, "provider-card": { "text-badge-enabled": "", "text-badge-not-enabled": "" @@ -2513,6 +2529,8 @@ "label-enabled": "", "reset-configuration": "", "reset-configuration-description": "", + "save": "", + "saving": "", "title-more-actions": "", "title-reset": "", "tooltip-more-actions": "" @@ -2653,6 +2671,9 @@ "canvas": { "not-found-display": { "not-found": "" + }, + "text-display": { + "double-click-to-set": "" } }, "carousel": { @@ -2929,7 +2950,9 @@ }, "annotation-settings-list": { "aria-label-delete": "", + "built-in": "", "data-source": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3042,6 +3065,7 @@ } }, "confirm-plugin-dashboard-save-modal": { + "body-plugin-dashboard": "", "cancel": "", "overwrite": "", "title-plugin-dashboard": "" @@ -3064,6 +3088,7 @@ } }, "dashboard-validation": { + "body-dashboard-failed-schema-validation": "", "title-checking-dashboard-validity": "", "title-dashboard-failed-schema-validation": "", "title-error-checking-dashboard-validity": "" @@ -3198,6 +3223,7 @@ }, "gen-aihistory": { "aria-label-send-custom-feedback": "", + "footer-text": "", "placeholder-tell-ai-what-to-do-next": "" }, "get-field-override-categories": { @@ -3288,9 +3314,13 @@ "make-editable": "" }, "minimalistic-pagination": { + "page-count": "", "tooltip-next": "", "tooltip-previous": "" }, + "new-dashboard-with-ds": { + "not-found": "" + }, "new-panel": { "configure-button": "", "configure-button-menu": "", @@ -3352,9 +3382,12 @@ "go-back-to-queries": "" }, "public-dashboard-not-available": { + "does-not-exist": "", + "paused": "", "try-again-later": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, "row-options-button": { @@ -3416,6 +3449,8 @@ "description": "", "label-copy-tags": "", "label-folder": "", + "save": "", + "saving": "", "title": "" }, "save-dashboard-button": { @@ -3432,19 +3467,30 @@ "label-details": "" }, "save-dashboard-error-proxy": { - "title-conflict": "" + "body-name-exists": "", + "body-version-mismatch": "", + "title-name-exists": "", + "title-version-mismatch": "" }, "save-dashboard-form": { "cancel": "", "label-current-range-dashboard-default": "", "label-current-variable-values-dashboard-default": "", "no-changes-to-save": "", - "placeholder-describe-changes": "" + "placeholder-describe-changes": "", + "save": "", + "saving": "" }, "save-provisioned-dashboard-form": { "cancel": "", + "cannot-be-saved": "", "copy-json-to-clipboard": "", - "save-json-to-file": "" + "file-path": "", + "save-json-to-file": "", + "see-docs": "" + }, + "share-public-dashboard-loader": { + "loading-configuration": "" }, "solo-panel": { "loading-initializing-dashboard": "", @@ -3623,6 +3669,9 @@ "title-delete": "" }, "transformation-picker": { + "info": "", + "info-graph-not-suitable": "", + "info-switch-to-table": "", "placeholder-search-for-transformation": "", "read-more": "", "title-transformations": "" @@ -3647,6 +3696,9 @@ "discard": "", "title-unsaved-changes": "" }, + "untheme-dashboard-row": { + "dashboard-datasource": "" + }, "unthemed-dashboard-row": { "aria-label-delete-row": "", "learn-more": "" @@ -3658,7 +3710,10 @@ "tags-expected-strings": "タグは文字列の配列を期待しています" }, "version-history-comparison": { - "label-view-json-diff": "" + "button-restore": "", + "label-view-json-diff": "", + "new-updated-by": "", + "old-updated-by": "" }, "version-history-table": { "aria-label-toggle-selection": "", @@ -3757,8 +3812,10 @@ "title-annotation-support-source": "" }, "annotation-settings-list": { + "built-in": "", "data-source": "", "delete-aria-label": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3819,6 +3876,8 @@ }, "data-source-variable-form": { "data-source-options": "", + "description-instance-name-filter": "", + "example-instance-name-filter": "", "selection-options": "" }, "description-label": { @@ -3879,7 +3938,12 @@ "title-plugin-dashboard": "", "title-someone-else-has-updated-this-dashboard": "", "would-still-dashboard": "" - } + }, + "save-and-overwrite": "" + }, + "library-viz-panel-info": { + "last-edited": "", + "usage-count_other": "" }, "name-already-exists-error": { "body-name-already-exists": "", @@ -3889,6 +3953,7 @@ "alert": { "title-errors-loading-rules": "" }, + "error-failed-to-load": "", "text-loading-rules": "", "title-dashboard-not-saved": "" }, @@ -3918,7 +3983,10 @@ "title-close": "" }, "provisioned-delete-modal": { + "cannot-be-deleted": "", + "file-path": "", "ok": "", + "see-docs": "", "title-cannot-delete-provisioned-dashboard": "" }, "provisioned-resource-delete-modal": { @@ -3931,13 +3999,21 @@ "query": "" }, "query-variable-editor-form": { + "description-examples": "", + "description-optional": "", "label-data-source": "", "query-options": "", "selection-options": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, + "save-button": { + "save": "", + "save-and-overwrite": "", + "saving": "" + }, "save-dashboard-as-form": { "aria-label-save-dashboard-description-field": "", "aria-label-save-dashboard-title-field": "", @@ -3990,17 +4066,20 @@ "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "affected-dashboards_other": "" }, "save-provisioned-dashboard-form": { "api-error": "ダッシュボードの保存中にエラーが発生しました", "api-success": "ダッシュボードの変更を保存しました", "cancel": "キャンセル", + "cannot-be-saved": "", "copy-json-message": "ターゲットに直接アクセスできる場合は、JSONをコピーしてそこに貼り付けます。", "copy-json-to-clipboard": "", "dashboard-comment-placeholder-describe-changes-optional": "変更内容を説明するメモを追加します(オプション)", "description-branch-name-in-git-hub": "GitHubのブランチ名", "description-inside-repository": "リポジトリ内のファイルパス(.json または .yaml)", + "file-path": "", "label-branch": "ブランチ", "label-comment": "コメント", "label-description": "説明", @@ -4011,6 +4090,7 @@ "save": "保存", "save-json-to-file": "", "saving": "保存中…", + "see-docs": "", "title-required": "ダッシュボードのタイトルが必要です", "title-same-as-folder": "ダッシュボード名をフォルダ名と同じにすることはできません", "title-this-repository-is-read-only": "このリポジトリは読み取り専用です", @@ -4029,6 +4109,9 @@ "share-button": { "aria-label-sharedropdownmenu": "" }, + "solo-panel-page": { + "loading": "" + }, "text-box-variable-form": { "placeholder-default-value-if-any": "", "text-options": "" @@ -4088,10 +4171,18 @@ "preview-of-values": "", "show-more": "" }, + "version-history": { + "comparison": { + "button-restore": "" + } + }, "version-history-comparison": { - "label-view-json-diff": "" + "label-view-json-diff": "", + "new-version-updated": "", + "old-version-updated": "" }, "version-history-header": { + "compare-versions": "", "latest": "", "tooltip-reset-version": "" }, @@ -4267,6 +4358,7 @@ }, "button-row": { "delete": "", + "save-and-test": "", "test": "" }, "cloud-info-box": { @@ -4290,6 +4382,9 @@ "data-source-picker": { "aria-label-select-a-data-source": "" }, + "data-source-plugin-config-page": { + "page-not-found": "" + }, "data-source-plugin-state": { "plugin-state": "" }, @@ -4426,8 +4521,12 @@ } }, "explore": { + "accordian-logs": { + "footer": "" + }, "accordian-references": { - "references": "" + "references": "", + "view-linked-span": "" }, "add-to-dashboard": "ダッシュボードに追加", "basic-extensions": { @@ -4436,6 +4535,7 @@ "clear": "", "confirm-navigation-modal": { "cancel": "", + "new-tab": "", "open": "", "open-in-new-tab": "" }, @@ -4456,9 +4556,14 @@ "add-transformation": "", "aria-label-delete-transformation": "", "aria-label-edit-transformation": "", + "body-correlation-details": "", + "expression": "", "label-description": "", + "label-description-header": "", "label-label": "", - "title-correlation-details": "" + "title-correlation-details": "", + "tooltip-transformations": "", + "transformations": "" }, "correlation-transformation-add-modal": { "add-transformation": "", @@ -4484,10 +4589,13 @@ "title-unsaved-changes-to-correlation": "" }, "draggable-manager-demo": { + "click-and-drag-gray-divider": "", "click-horizontally-somewhere-colored-below": "", + "drag-value": "", "draggable-manager-demo": "", "dragging-a-divider": "", - "dragging-a-sub-region": "" + "dragging-a-sub-region": "", + "value": "" }, "drilldownInfo": { "action": "Grafanaドリルダウンに移動", @@ -4518,6 +4626,10 @@ "outline": "", "tooltip-content-outline": "" }, + "feature-toggle-page": { + "description-explore-disabled": "", + "title-explore-disabled": "" + }, "get-matches-metadata": { "matches": "" }, @@ -4528,10 +4640,15 @@ }, "live-logs": { "clear-logs": "", - "exit-live-mode": "" + "exit-live-mode": "", + "last-line-received": "", + "pause": "", + "resume": "" }, "live-tail-button": { + "live": "", "pause-the-live-stream": "", + "paused": "", "start-live-stream-your-logs": "", "stop-and-exit-the-live-stream": "" }, @@ -4560,12 +4677,14 @@ "show-original-line": "" }, "logs-sample-panel": { + "label": "", "logs-sample-is-loading": "", "no-logs-sample-data": "", "open-in-split-view-button": { "open-logs-in-split-view": "" }, - "title-failed-sample-query": "" + "title-failed-sample-query": "", + "tooltip": "" }, "logs-table-available-fields": { "title-label-percentage": "" @@ -4574,7 +4693,9 @@ "no-fields": "" }, "logs-table-multi-select": { - "fields": "" + "fields": "", + "reset": "", + "selected-fields": "" }, "logs-table-nav-field": { "aria-label-drag-and-drop-icon": "", @@ -4593,13 +4714,20 @@ "title-showing-partial-data": "", "title-unable-to-show-log-volume": "" }, + "logs-volumne-panel-list": { + "body-no-logs-volume-available": "" + }, "make-datasource-setup": { "aria-label-query": "" }, "next": "", "next-prev-result": { "aria-label-next": "", - "aria-label-prev": "" + "aria-label-prev": "", + "depth": "", + "depth-span-filter-matches": "", + "services": "", + "services-span-filter-matches": "" }, "no-data-source-call-to-action": { "cta-element": { @@ -4615,6 +4743,7 @@ "aria-label-go-queryless": "" }, "raw-list-container": { + "item-count": "", "label-expand-results": "" }, "raw-list-item": { @@ -4630,7 +4759,6 @@ "newest-first": "新しいものから表示", "oldest-first": "古いものから表示", "query-history": "クエリ履歴", - "query-library": "ライブラリをクエリする", "settings": "設定", "starred": "スター付き" }, @@ -4746,6 +4874,9 @@ }, "show-critical-path-only": "", "show-matches-only": "", + "span-bar": { + "tooltip-critical-path": "" + }, "span-bar-section": { "description-link": "", "title-span-bar": "" @@ -4772,6 +4903,7 @@ "aria-label-select-service-name-operator": "", "aria-label-select-span-name": "", "aria-label-select-span-name-operator": "", + "label-collapse": "", "label-duration": "", "label-service-name": "", "label-span-name": "", @@ -4815,6 +4947,9 @@ "tooltip-expand": "", "tooltip-expand-all": "" }, + "timeline-header-row": { + "title": "" + }, "toolbar": { "add-to-extensions": "追加", "add-to-queryless-extensions": "クエリレスにする", @@ -4844,7 +4979,8 @@ "title-share-thoughts-about-tracing-grafana": "" }, "trace-page-header": { - "text-partial-trace": "" + "text-partial-trace": "", + "tooltip-url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "", @@ -4858,6 +4994,7 @@ "title-trace": "" }, "unconnected-node-graph-container": { + "count-warning": "", "title-node-graph": "" }, "unthemed-logs": { @@ -4911,11 +5048,20 @@ "classic-conditions": { "label-conditions": "" }, + "condition": { + "of": "", + "to": "", + "when": "" + }, "expression-query-editor": { "label-operation": "" }, "math": { - "available-math-functions": "" + "available-math-functions": "", + "run-math-operations": "", + "tooltip-footer": "", + "tooltip-title": "", + "tooltip-trigger": "" }, "reduce": { "label-function": "", @@ -4994,6 +5140,12 @@ }, "essentials": { "title-essentials": "" + }, + "progress-bar": { + "steps-status": "" + }, + "progress-status": { + "your-progress": "" } }, "grafana": { @@ -5385,8 +5537,14 @@ }, "inspector": { "inspect-data-tab": { + "loading": "", "no-data": "" }, + "inspect-error-tab": { + "error-status-message": "", + "error-status-no-message": "", + "error-trace-message": "" + }, "inspect-jsontab": { "apply": "", "support": "" @@ -5401,7 +5559,9 @@ }, "query-inspector": { "query-inspector": "", - "text-loading-query-inspector": "" + "text-loading-query-inspector": "", + "count-frames_other": "", + "count-rows_other": "" } }, "invites": { @@ -5413,6 +5573,11 @@ "name": "" }, "signup-invited-page": { + "complete-following": "", + "custom-has-invited-you": "", + "default-has-invited-you": "", + "greeting-custom": "", + "greeting-default": "", "label-email": "", "label-name": "", "label-password": "", @@ -5608,6 +5773,10 @@ "has-connected-dashboards": { "dashboard-name": "" }, + "library-panel-info": { + "last-edited": "", + "usage-count_other": "" + }, "library-panels-search": { "placeholder-search-by-name-or-description": "" }, @@ -5633,12 +5802,14 @@ "success": "ライブラリパネルを保存しました" }, "save-library-panel-modal": { + "affected-dashboards": "", "cancel": "", "dashboard-name": "", "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "num-affected_other": "" } }, "link": { @@ -5663,6 +5834,7 @@ "live": { "dashboard-changed-modal": { "continue-editing": "", + "description": "", "discard-local-changes": "", "title-dashboard-changed": "" }, @@ -5741,6 +5913,9 @@ "collapse": "", "expand": "" }, + "log-labels-list": { + "log-line": "" + }, "log-line-menu": { "copy-link": "ログ行へのリンクをコピー", "copy-log": "ログ行をコピー", @@ -5830,6 +6005,8 @@ "aria-label-no-details": "" }, "un-themed-log-details-row": { + "filter-out": "", + "filter-out-query": "", "title-copy-value-to-clipboard": "", "toggle-field-button": { "tooltip-field-instead-message": "", @@ -5837,6 +6014,10 @@ }, "tooltip-adhoc-statistics": "", "tooltip-hide-adhoc-statistics": "" + }, + "un-themed-log-label-stats": { + "field-log-stats": "", + "label-log-stats": "" } }, "manage-dashbaords": { @@ -5858,6 +6039,7 @@ "options": "" }, "import-dashboard-overview-un-connected": { + "importing-from": "", "published-by": "", "updated-on": "" }, @@ -5917,6 +6099,9 @@ "button": "このインスタンスをクラウドに移行する", "header": "Grafanaスタックを管理させてください" }, + "dashboard-info": { + "dashboard": "" + }, "delete-migration-token-confirm": { "body": "このトークンを自己管理インストールで既に使用している場合、そのインストールではコンテンツをアップロードできなくなります。", "confirm-button": "トークンを削除", @@ -5932,6 +6117,7 @@ "title": "クラウドスタックから切断する" }, "folder-info": { + "folder": "", "unable-to-load-folder": "" }, "get-started": { @@ -6554,6 +6740,7 @@ "org": { "new-org-page": { "create": "", + "description": "", "label-organization-name": "", "placeholder-org-name": "" }, @@ -6562,6 +6749,9 @@ "label-organization-profile": "", "update-organization-name": "" }, + "select-org-page": { + "description": "" + }, "user-invite-form": { "back": "", "label-email-or-username": "", @@ -6569,7 +6759,11 @@ "label-send-invite-email": "", "placeholder-optional": "", "role": "", - "submit": "" + "submit": "", + "tooltip": "" + }, + "user-invite-page": { + "sub-title": "" } }, "org-picker": { @@ -6612,7 +6806,9 @@ "title-not-found": "" }, "panel-renderer": { + "failed-to-load-plugin": "", "loading-plugin-panel": "", + "no-panel-component": "", "no-panel-data": "" }, "panel-type-card": { @@ -6639,7 +6835,14 @@ "select-placeholder": "" }, "playlist": { + "playlist-table-rows": { + "aria-label-playlist-item": "", + "multiple-dashboards-found": "", + "no-dashboards-found": "", + "not-found": "" + }, "start-modal": { + "button-start": "", "description-customize-dashboard-elements-visibility": "", "description-panel-heights-adjusted-screen": "", "label-autofit": "", @@ -6699,6 +6902,11 @@ "message": "プレイリストが見つかりません" } }, + "plugin": { + "plugin-details-deprecated-warning": { + "body-deprecated": "" + } + }, "plugins": { "app-root-page": { "access-denied": { @@ -6802,6 +7010,12 @@ "add-new-data-source": "", "title-button-disabled": "" }, + "install-controls": { + "install": "", + "installing": "", + "update": "", + "updating": "" + }, "install-controls-button": { "title-uninstall-modal": "" }, @@ -6820,6 +7034,7 @@ "label-severity": "" }, "not-found-plugin": { + "body-plugin-not-found": "", "title-plugin-not-found": "" }, "plugin-actions": { @@ -6834,12 +7049,17 @@ "tooltip-plugin-deprecated-longer-receives-updates": "" }, "plugin-details-body": { + "needs-service-account": "", "page-not-found": "" }, "plugin-details-deprecated-warning": { "title-deprecated": "" }, + "plugin-details-header-dependencies": { + "grafana-dependency": "" + }, "plugin-details-signature": { + "body-invalid-plugin-signature": "", "read-more-about-plugins-signing": "", "title-invalid-plugin-signature": "" }, @@ -6870,6 +7090,8 @@ "update-available": "" }, "plugin-usage": { + "body-missing-feature-toggle-panel-title-search": "", + "num-usages": "", "title-missing-feature-toggle-panel-title-search": "", "title-not-used-yet": "" }, @@ -6886,7 +7108,9 @@ }, "version-list": { "grafana-dependency": "", + "installed-version": "", "last-updated": "", + "latest-compatible-version": "", "no-version-history-was-found": "", "version": "" } @@ -6909,6 +7133,12 @@ }, "change-theme": "テーマを変更", "enable-kiosk-mode": "", + "feature-toggle-page": { + "profile-not-enabled": "" + }, + "feature-toggles-age": { + "enable-in-config": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -7052,6 +7282,8 @@ "repository-config-exists-configuration": "" }, "file-status-page": { + "save": "", + "saving": "", "title-error-loading-file": "" }, "files-view": { @@ -7082,7 +7314,6 @@ }, "getting-started": { "alert-temporary-outage": "", - "engaging-graphic": "", "modal-description-public-access": "", "modal-description-required-features": "", "modal-title-set-up-public-access": "", @@ -7178,13 +7409,16 @@ "checked": "", "finished": "", "health": "", + "healthy": "", "job-id": "", "last-ref": "", "messages": "", + "not-available": "", "pull-status": "", "resources": "", "started": "", "status": "", + "unhealthy": "", "view-folder": "", "webhook": "", "webhook-events": "", @@ -7474,31 +7708,41 @@ "hidden": "", "query-name-div-title-edit-query-name": "" }, + "query-error-alert": { + "trace-id": "" + }, "query-group": { "add-query": "", "expression": "", "title-data-source-help": "" }, "query-group-options-editor": { + "collapsed-interval": "", + "collapsed-max-data-points": "", "hide-time-info": "", "Query options-title-query-options": "", "relative-time": "", + "relative-time-tooltip": "", "render-cache-timeout-option": { "cache-timeout": "" }, "render-interval-option": { "interval": "", + "interval-tooltip": "", "min-interval": "", + "min-interval-tooltip": "", "time-range-max-data-points": "" }, "render-max-data-points-option": { "max-data-points": "", + "max-data-points-tooltip": "", "width-of-panel": "" }, "render-query-caching-ttloption": { "cache-ttl": "" }, - "time-shift": "" + "time-shift": "", + "time-shift-tooltip": "" }, "query-group-top-section": { "data-source": "", @@ -7677,6 +7921,9 @@ } }, "search": { + "action-row": { + "panel-type": "" + }, "actions": { "include-panels": "パネルを含める", "remove-datasource-filter": "データソース:{{datasource}}", @@ -7800,6 +8047,9 @@ "label-roles": "", "used-by": "" }, + "service-account-profile-row": { + "edit": "" + }, "service-account-role-row": { "aria-label-role": "" }, @@ -8324,6 +8574,7 @@ "generate-enum-values-from-data": "" }, "enum-mapping-row": { + "click-to-edit": "", "remove-enum-row-aria-label-delete-enum-row": "", "remove-enum-row-tooltip-delete": "" }, @@ -8378,10 +8629,13 @@ "label-format": "", "label-set-timezone": "", "label-time-field": "", + "tooltip-format": "", "tooltip-timezone-manually": "" }, "get-tooltips": { - "json-value": "" + "description": "", + "json-value": "", + "valid-paths": "" }, "group-by-field-configuration": { "placeholder-ignored": "", @@ -8598,6 +8852,7 @@ "message": "ユーザーが見つかりません" }, "token-revoked-modal": { + "auto-revoked": "", "resume-message": "", "sign-in": "", "title-you-have-been-automatically-signed-out": "" @@ -8692,7 +8947,8 @@ "general": "", "placeholder-descriptive-text": "", "placeholder-label-name": "", - "placeholder-variable-name": "" + "placeholder-variable-name": "", + "run-query": "" }, "variable-editor-list": { "definition": "", diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index 5918faac059..f7f39cf8892 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -1,6 +1,5 @@ { "_comment": "올바른 영어 표현의 기준은 코드입니다. 이는 구성 요소에서 직접 업데이트되어야 하며, 이 파일에서 명시한 추가 복수형도 마찬가지입니다.", - "{{pageStart}} - {{pageEnd}} of {{numPages}}": "", "access-control": { "add-permission": { "built-in-aria-label": "", @@ -963,7 +962,8 @@ "expression-result": { "aria-label-nextpage": "", "aria-label-previouspage": "", - "no-data": "" + "no-data": "", + "page-counter": "" }, "federated-rule-warning": { "experimental": "", @@ -1994,6 +1994,10 @@ "title": "경고 규칙" }, "rulerrule-loading-error": "규칙 로딩 실패", + "toggle": { + "go-back-to-old-look": "", + "try-out-the-new-look": "" + }, "unknown-rule-type": "" }, "rule-list-errors": { @@ -2500,6 +2504,18 @@ "configuration-required": "", "refer-documentation-configure-authentication": "" }, + "fields": { + "allowed-groups-description": "", + "allowed-groups-description-oauth": "", + "api-url-description": "", + "team-ids-description": "", + "team-ids-description-oauth": "", + "team-ids-github": "", + "team-ids-other": "", + "teams-url-description": "", + "teams-url-description-oauth": "", + "use-pkce-description": "" + }, "provider-card": { "text-badge-enabled": "", "text-badge-not-enabled": "" @@ -2513,6 +2529,8 @@ "label-enabled": "", "reset-configuration": "", "reset-configuration-description": "", + "save": "", + "saving": "", "title-more-actions": "", "title-reset": "", "tooltip-more-actions": "" @@ -2653,6 +2671,9 @@ "canvas": { "not-found-display": { "not-found": "" + }, + "text-display": { + "double-click-to-set": "" } }, "carousel": { @@ -2929,7 +2950,9 @@ }, "annotation-settings-list": { "aria-label-delete": "", + "built-in": "", "data-source": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3042,6 +3065,7 @@ } }, "confirm-plugin-dashboard-save-modal": { + "body-plugin-dashboard": "", "cancel": "", "overwrite": "", "title-plugin-dashboard": "" @@ -3064,6 +3088,7 @@ } }, "dashboard-validation": { + "body-dashboard-failed-schema-validation": "", "title-checking-dashboard-validity": "", "title-dashboard-failed-schema-validation": "", "title-error-checking-dashboard-validity": "" @@ -3198,6 +3223,7 @@ }, "gen-aihistory": { "aria-label-send-custom-feedback": "", + "footer-text": "", "placeholder-tell-ai-what-to-do-next": "" }, "get-field-override-categories": { @@ -3288,9 +3314,13 @@ "make-editable": "" }, "minimalistic-pagination": { + "page-count": "", "tooltip-next": "", "tooltip-previous": "" }, + "new-dashboard-with-ds": { + "not-found": "" + }, "new-panel": { "configure-button": "", "configure-button-menu": "", @@ -3352,9 +3382,12 @@ "go-back-to-queries": "" }, "public-dashboard-not-available": { + "does-not-exist": "", + "paused": "", "try-again-later": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, "row-options-button": { @@ -3416,6 +3449,8 @@ "description": "", "label-copy-tags": "", "label-folder": "", + "save": "", + "saving": "", "title": "" }, "save-dashboard-button": { @@ -3432,19 +3467,30 @@ "label-details": "" }, "save-dashboard-error-proxy": { - "title-conflict": "" + "body-name-exists": "", + "body-version-mismatch": "", + "title-name-exists": "", + "title-version-mismatch": "" }, "save-dashboard-form": { "cancel": "", "label-current-range-dashboard-default": "", "label-current-variable-values-dashboard-default": "", "no-changes-to-save": "", - "placeholder-describe-changes": "" + "placeholder-describe-changes": "", + "save": "", + "saving": "" }, "save-provisioned-dashboard-form": { "cancel": "", + "cannot-be-saved": "", "copy-json-to-clipboard": "", - "save-json-to-file": "" + "file-path": "", + "save-json-to-file": "", + "see-docs": "" + }, + "share-public-dashboard-loader": { + "loading-configuration": "" }, "solo-panel": { "loading-initializing-dashboard": "", @@ -3623,6 +3669,9 @@ "title-delete": "" }, "transformation-picker": { + "info": "", + "info-graph-not-suitable": "", + "info-switch-to-table": "", "placeholder-search-for-transformation": "", "read-more": "", "title-transformations": "" @@ -3647,6 +3696,9 @@ "discard": "", "title-unsaved-changes": "" }, + "untheme-dashboard-row": { + "dashboard-datasource": "" + }, "unthemed-dashboard-row": { "aria-label-delete-row": "", "learn-more": "" @@ -3658,7 +3710,10 @@ "tags-expected-strings": "태그로 문자열 배열을 입력해야 합니다." }, "version-history-comparison": { - "label-view-json-diff": "" + "button-restore": "", + "label-view-json-diff": "", + "new-updated-by": "", + "old-updated-by": "" }, "version-history-table": { "aria-label-toggle-selection": "", @@ -3757,8 +3812,10 @@ "title-annotation-support-source": "" }, "annotation-settings-list": { + "built-in": "", "data-source": "", "delete-aria-label": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3819,6 +3876,8 @@ }, "data-source-variable-form": { "data-source-options": "", + "description-instance-name-filter": "", + "example-instance-name-filter": "", "selection-options": "" }, "description-label": { @@ -3879,7 +3938,12 @@ "title-plugin-dashboard": "", "title-someone-else-has-updated-this-dashboard": "", "would-still-dashboard": "" - } + }, + "save-and-overwrite": "" + }, + "library-viz-panel-info": { + "last-edited": "", + "usage-count_other": "" }, "name-already-exists-error": { "body-name-already-exists": "", @@ -3889,6 +3953,7 @@ "alert": { "title-errors-loading-rules": "" }, + "error-failed-to-load": "", "text-loading-rules": "", "title-dashboard-not-saved": "" }, @@ -3918,7 +3983,10 @@ "title-close": "" }, "provisioned-delete-modal": { + "cannot-be-deleted": "", + "file-path": "", "ok": "", + "see-docs": "", "title-cannot-delete-provisioned-dashboard": "" }, "provisioned-resource-delete-modal": { @@ -3931,13 +3999,21 @@ "query": "" }, "query-variable-editor-form": { + "description-examples": "", + "description-optional": "", "label-data-source": "", "query-options": "", "selection-options": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, + "save-button": { + "save": "", + "save-and-overwrite": "", + "saving": "" + }, "save-dashboard-as-form": { "aria-label-save-dashboard-description-field": "", "aria-label-save-dashboard-title-field": "", @@ -3990,17 +4066,20 @@ "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "affected-dashboards_other": "" }, "save-provisioned-dashboard-form": { "api-error": "대시보드 저장 중 오류 발생", "api-success": "대시보드 변경 사항 저장됨", "cancel": "취소", + "cannot-be-saved": "", "copy-json-message": "대상에 직접 액세스할 수 있는 경우 JSON을 복사하여 붙여넣으세요.", "copy-json-to-clipboard": "", "dashboard-comment-placeholder-describe-changes-optional": "변경 사항을 설명하는 메모 추가(선택 사항)", "description-branch-name-in-git-hub": "GitHub의 브랜치 이름", "description-inside-repository": "리포지토리 내부의 파일 경로(.json 또는 .yaml)", + "file-path": "", "label-branch": "브랜치", "label-comment": "코멘트", "label-description": "설명", @@ -4011,6 +4090,7 @@ "save": "저장", "save-json-to-file": "", "saving": "저장 중...", + "see-docs": "", "title-required": "대시보드 제목은 필수 입력 항목입니다.", "title-same-as-folder": "대시보드 이름은 폴더 이름과 같을 수 없습니다.", "title-this-repository-is-read-only": "이 리포지토리는 읽기 전용입니다.", @@ -4029,6 +4109,9 @@ "share-button": { "aria-label-sharedropdownmenu": "" }, + "solo-panel-page": { + "loading": "" + }, "text-box-variable-form": { "placeholder-default-value-if-any": "", "text-options": "" @@ -4088,10 +4171,18 @@ "preview-of-values": "", "show-more": "" }, + "version-history": { + "comparison": { + "button-restore": "" + } + }, "version-history-comparison": { - "label-view-json-diff": "" + "label-view-json-diff": "", + "new-version-updated": "", + "old-version-updated": "" }, "version-history-header": { + "compare-versions": "", "latest": "", "tooltip-reset-version": "" }, @@ -4267,6 +4358,7 @@ }, "button-row": { "delete": "", + "save-and-test": "", "test": "" }, "cloud-info-box": { @@ -4290,6 +4382,9 @@ "data-source-picker": { "aria-label-select-a-data-source": "" }, + "data-source-plugin-config-page": { + "page-not-found": "" + }, "data-source-plugin-state": { "plugin-state": "" }, @@ -4426,8 +4521,12 @@ } }, "explore": { + "accordian-logs": { + "footer": "" + }, "accordian-references": { - "references": "" + "references": "", + "view-linked-span": "" }, "add-to-dashboard": "대시보드에 추가", "basic-extensions": { @@ -4436,6 +4535,7 @@ "clear": "", "confirm-navigation-modal": { "cancel": "", + "new-tab": "", "open": "", "open-in-new-tab": "" }, @@ -4456,9 +4556,14 @@ "add-transformation": "", "aria-label-delete-transformation": "", "aria-label-edit-transformation": "", + "body-correlation-details": "", + "expression": "", "label-description": "", + "label-description-header": "", "label-label": "", - "title-correlation-details": "" + "title-correlation-details": "", + "tooltip-transformations": "", + "transformations": "" }, "correlation-transformation-add-modal": { "add-transformation": "", @@ -4484,10 +4589,13 @@ "title-unsaved-changes-to-correlation": "" }, "draggable-manager-demo": { + "click-and-drag-gray-divider": "", "click-horizontally-somewhere-colored-below": "", + "drag-value": "", "draggable-manager-demo": "", "dragging-a-divider": "", - "dragging-a-sub-region": "" + "dragging-a-sub-region": "", + "value": "" }, "drilldownInfo": { "action": "Grafana 드릴다운으로 이동", @@ -4518,6 +4626,10 @@ "outline": "", "tooltip-content-outline": "" }, + "feature-toggle-page": { + "description-explore-disabled": "", + "title-explore-disabled": "" + }, "get-matches-metadata": { "matches": "" }, @@ -4528,10 +4640,15 @@ }, "live-logs": { "clear-logs": "", - "exit-live-mode": "" + "exit-live-mode": "", + "last-line-received": "", + "pause": "", + "resume": "" }, "live-tail-button": { + "live": "", "pause-the-live-stream": "", + "paused": "", "start-live-stream-your-logs": "", "stop-and-exit-the-live-stream": "" }, @@ -4560,12 +4677,14 @@ "show-original-line": "" }, "logs-sample-panel": { + "label": "", "logs-sample-is-loading": "", "no-logs-sample-data": "", "open-in-split-view-button": { "open-logs-in-split-view": "" }, - "title-failed-sample-query": "" + "title-failed-sample-query": "", + "tooltip": "" }, "logs-table-available-fields": { "title-label-percentage": "" @@ -4574,7 +4693,9 @@ "no-fields": "" }, "logs-table-multi-select": { - "fields": "" + "fields": "", + "reset": "", + "selected-fields": "" }, "logs-table-nav-field": { "aria-label-drag-and-drop-icon": "", @@ -4593,13 +4714,20 @@ "title-showing-partial-data": "", "title-unable-to-show-log-volume": "" }, + "logs-volumne-panel-list": { + "body-no-logs-volume-available": "" + }, "make-datasource-setup": { "aria-label-query": "" }, "next": "", "next-prev-result": { "aria-label-next": "", - "aria-label-prev": "" + "aria-label-prev": "", + "depth": "", + "depth-span-filter-matches": "", + "services": "", + "services-span-filter-matches": "" }, "no-data-source-call-to-action": { "cta-element": { @@ -4615,6 +4743,7 @@ "aria-label-go-queryless": "" }, "raw-list-container": { + "item-count": "", "label-expand-results": "" }, "raw-list-item": { @@ -4630,7 +4759,6 @@ "newest-first": "최신순", "oldest-first": "오래된 순", "query-history": "쿼리 이력", - "query-library": "쿼리 라이브러리", "settings": "설정", "starred": "별표 표시됨" }, @@ -4746,6 +4874,9 @@ }, "show-critical-path-only": "", "show-matches-only": "", + "span-bar": { + "tooltip-critical-path": "" + }, "span-bar-section": { "description-link": "", "title-span-bar": "" @@ -4772,6 +4903,7 @@ "aria-label-select-service-name-operator": "", "aria-label-select-span-name": "", "aria-label-select-span-name-operator": "", + "label-collapse": "", "label-duration": "", "label-service-name": "", "label-span-name": "", @@ -4815,6 +4947,9 @@ "tooltip-expand": "", "tooltip-expand-all": "" }, + "timeline-header-row": { + "title": "" + }, "toolbar": { "add-to-extensions": "추가", "add-to-queryless-extensions": "쿼리 없이 진행", @@ -4844,7 +4979,8 @@ "title-share-thoughts-about-tracing-grafana": "" }, "trace-page-header": { - "text-partial-trace": "" + "text-partial-trace": "", + "tooltip-url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "", @@ -4858,6 +4994,7 @@ "title-trace": "" }, "unconnected-node-graph-container": { + "count-warning": "", "title-node-graph": "" }, "unthemed-logs": { @@ -4911,11 +5048,20 @@ "classic-conditions": { "label-conditions": "" }, + "condition": { + "of": "", + "to": "", + "when": "" + }, "expression-query-editor": { "label-operation": "" }, "math": { - "available-math-functions": "" + "available-math-functions": "", + "run-math-operations": "", + "tooltip-footer": "", + "tooltip-title": "", + "tooltip-trigger": "" }, "reduce": { "label-function": "", @@ -4994,6 +5140,12 @@ }, "essentials": { "title-essentials": "" + }, + "progress-bar": { + "steps-status": "" + }, + "progress-status": { + "your-progress": "" } }, "grafana": { @@ -5385,8 +5537,14 @@ }, "inspector": { "inspect-data-tab": { + "loading": "", "no-data": "" }, + "inspect-error-tab": { + "error-status-message": "", + "error-status-no-message": "", + "error-trace-message": "" + }, "inspect-jsontab": { "apply": "", "support": "" @@ -5401,7 +5559,9 @@ }, "query-inspector": { "query-inspector": "", - "text-loading-query-inspector": "" + "text-loading-query-inspector": "", + "count-frames_other": "", + "count-rows_other": "" } }, "invites": { @@ -5413,6 +5573,11 @@ "name": "" }, "signup-invited-page": { + "complete-following": "", + "custom-has-invited-you": "", + "default-has-invited-you": "", + "greeting-custom": "", + "greeting-default": "", "label-email": "", "label-name": "", "label-password": "", @@ -5608,6 +5773,10 @@ "has-connected-dashboards": { "dashboard-name": "" }, + "library-panel-info": { + "last-edited": "", + "usage-count_other": "" + }, "library-panels-search": { "placeholder-search-by-name-or-description": "" }, @@ -5633,12 +5802,14 @@ "success": "라이브러리 패널 저장됨" }, "save-library-panel-modal": { + "affected-dashboards": "", "cancel": "", "dashboard-name": "", "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "num-affected_other": "" } }, "link": { @@ -5663,6 +5834,7 @@ "live": { "dashboard-changed-modal": { "continue-editing": "", + "description": "", "discard-local-changes": "", "title-dashboard-changed": "" }, @@ -5741,6 +5913,9 @@ "collapse": "", "expand": "" }, + "log-labels-list": { + "log-line": "" + }, "log-line-menu": { "copy-link": "링크를 로그 라인에 복사", "copy-log": "로그 라인 복사", @@ -5830,6 +6005,8 @@ "aria-label-no-details": "" }, "un-themed-log-details-row": { + "filter-out": "", + "filter-out-query": "", "title-copy-value-to-clipboard": "", "toggle-field-button": { "tooltip-field-instead-message": "", @@ -5837,6 +6014,10 @@ }, "tooltip-adhoc-statistics": "", "tooltip-hide-adhoc-statistics": "" + }, + "un-themed-log-label-stats": { + "field-log-stats": "", + "label-log-stats": "" } }, "manage-dashbaords": { @@ -5858,6 +6039,7 @@ "options": "" }, "import-dashboard-overview-un-connected": { + "importing-from": "", "published-by": "", "updated-on": "" }, @@ -5917,6 +6099,9 @@ "button": "이 인스턴스를 클라우드로 마이그레이션", "header": "Grafana 스택은 자동으로 관리됩니다." }, + "dashboard-info": { + "dashboard": "" + }, "delete-migration-token-confirm": { "body": "이 토큰을 이미 자체 관리되는 설치에 사용한 경우, 해당 설치에서는 더 이상 콘텐츠를 업로드할 수 없습니다.", "confirm-button": "토큰 삭제", @@ -5932,6 +6117,7 @@ "title": "클라우드 스택에서 연결 해제" }, "folder-info": { + "folder": "", "unable-to-load-folder": "" }, "get-started": { @@ -6554,6 +6740,7 @@ "org": { "new-org-page": { "create": "", + "description": "", "label-organization-name": "", "placeholder-org-name": "" }, @@ -6562,6 +6749,9 @@ "label-organization-profile": "", "update-organization-name": "" }, + "select-org-page": { + "description": "" + }, "user-invite-form": { "back": "", "label-email-or-username": "", @@ -6569,7 +6759,11 @@ "label-send-invite-email": "", "placeholder-optional": "", "role": "", - "submit": "" + "submit": "", + "tooltip": "" + }, + "user-invite-page": { + "sub-title": "" } }, "org-picker": { @@ -6612,7 +6806,9 @@ "title-not-found": "" }, "panel-renderer": { + "failed-to-load-plugin": "", "loading-plugin-panel": "", + "no-panel-component": "", "no-panel-data": "" }, "panel-type-card": { @@ -6639,7 +6835,14 @@ "select-placeholder": "" }, "playlist": { + "playlist-table-rows": { + "aria-label-playlist-item": "", + "multiple-dashboards-found": "", + "no-dashboards-found": "", + "not-found": "" + }, "start-modal": { + "button-start": "", "description-customize-dashboard-elements-visibility": "", "description-panel-heights-adjusted-screen": "", "label-autofit": "", @@ -6699,6 +6902,11 @@ "message": "찾은 플레이리스트 없음" } }, + "plugin": { + "plugin-details-deprecated-warning": { + "body-deprecated": "" + } + }, "plugins": { "app-root-page": { "access-denied": { @@ -6802,6 +7010,12 @@ "add-new-data-source": "", "title-button-disabled": "" }, + "install-controls": { + "install": "", + "installing": "", + "update": "", + "updating": "" + }, "install-controls-button": { "title-uninstall-modal": "" }, @@ -6820,6 +7034,7 @@ "label-severity": "" }, "not-found-plugin": { + "body-plugin-not-found": "", "title-plugin-not-found": "" }, "plugin-actions": { @@ -6834,12 +7049,17 @@ "tooltip-plugin-deprecated-longer-receives-updates": "" }, "plugin-details-body": { + "needs-service-account": "", "page-not-found": "" }, "plugin-details-deprecated-warning": { "title-deprecated": "" }, + "plugin-details-header-dependencies": { + "grafana-dependency": "" + }, "plugin-details-signature": { + "body-invalid-plugin-signature": "", "read-more-about-plugins-signing": "", "title-invalid-plugin-signature": "" }, @@ -6870,6 +7090,8 @@ "update-available": "" }, "plugin-usage": { + "body-missing-feature-toggle-panel-title-search": "", + "num-usages": "", "title-missing-feature-toggle-panel-title-search": "", "title-not-used-yet": "" }, @@ -6886,7 +7108,9 @@ }, "version-list": { "grafana-dependency": "", + "installed-version": "", "last-updated": "", + "latest-compatible-version": "", "no-version-history-was-found": "", "version": "" } @@ -6909,6 +7133,12 @@ }, "change-theme": "테마 변경", "enable-kiosk-mode": "", + "feature-toggle-page": { + "profile-not-enabled": "" + }, + "feature-toggles-age": { + "enable-in-config": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -7052,6 +7282,8 @@ "repository-config-exists-configuration": "" }, "file-status-page": { + "save": "", + "saving": "", "title-error-loading-file": "" }, "files-view": { @@ -7082,7 +7314,6 @@ }, "getting-started": { "alert-temporary-outage": "", - "engaging-graphic": "", "modal-description-public-access": "", "modal-description-required-features": "", "modal-title-set-up-public-access": "", @@ -7178,13 +7409,16 @@ "checked": "", "finished": "", "health": "", + "healthy": "", "job-id": "", "last-ref": "", "messages": "", + "not-available": "", "pull-status": "", "resources": "", "started": "", "status": "", + "unhealthy": "", "view-folder": "", "webhook": "", "webhook-events": "", @@ -7474,31 +7708,41 @@ "hidden": "", "query-name-div-title-edit-query-name": "" }, + "query-error-alert": { + "trace-id": "" + }, "query-group": { "add-query": "", "expression": "", "title-data-source-help": "" }, "query-group-options-editor": { + "collapsed-interval": "", + "collapsed-max-data-points": "", "hide-time-info": "", "Query options-title-query-options": "", "relative-time": "", + "relative-time-tooltip": "", "render-cache-timeout-option": { "cache-timeout": "" }, "render-interval-option": { "interval": "", + "interval-tooltip": "", "min-interval": "", + "min-interval-tooltip": "", "time-range-max-data-points": "" }, "render-max-data-points-option": { "max-data-points": "", + "max-data-points-tooltip": "", "width-of-panel": "" }, "render-query-caching-ttloption": { "cache-ttl": "" }, - "time-shift": "" + "time-shift": "", + "time-shift-tooltip": "" }, "query-group-top-section": { "data-source": "", @@ -7677,6 +7921,9 @@ } }, "search": { + "action-row": { + "panel-type": "" + }, "actions": { "include-panels": "패널 포함", "remove-datasource-filter": "데이터 소스: {{datasource}}", @@ -7800,6 +8047,9 @@ "label-roles": "", "used-by": "" }, + "service-account-profile-row": { + "edit": "" + }, "service-account-role-row": { "aria-label-role": "" }, @@ -8324,6 +8574,7 @@ "generate-enum-values-from-data": "" }, "enum-mapping-row": { + "click-to-edit": "", "remove-enum-row-aria-label-delete-enum-row": "", "remove-enum-row-tooltip-delete": "" }, @@ -8378,10 +8629,13 @@ "label-format": "", "label-set-timezone": "", "label-time-field": "", + "tooltip-format": "", "tooltip-timezone-manually": "" }, "get-tooltips": { - "json-value": "" + "description": "", + "json-value": "", + "valid-paths": "" }, "group-by-field-configuration": { "placeholder-ignored": "", @@ -8598,6 +8852,7 @@ "message": "찾은 사용자 없음" }, "token-revoked-modal": { + "auto-revoked": "", "resume-message": "", "sign-in": "", "title-you-have-been-automatically-signed-out": "" @@ -8692,7 +8947,8 @@ "general": "", "placeholder-descriptive-text": "", "placeholder-label-name": "", - "placeholder-variable-name": "" + "placeholder-variable-name": "", + "run-query": "" }, "variable-editor-list": { "definition": "", diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index e459143a080..9d18a740242 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -1,6 +1,5 @@ { "_comment": "De code is de bron van waarheid voor Engelse zinnen. Ze moeten rechtstreeks worden bijgewerkt in de componenten en extra meervoudsvormen die in dit bestand worden gespecificeerd.", - "{{pageStart}} - {{pageEnd}} of {{numPages}}": "", "access-control": { "add-permission": { "built-in-aria-label": "", @@ -969,7 +968,8 @@ "expression-result": { "aria-label-nextpage": "", "aria-label-previouspage": "", - "no-data": "" + "no-data": "", + "page-counter": "" }, "federated-rule-warning": { "experimental": "", @@ -2004,6 +2004,10 @@ "title": "Waarschuwingsregels" }, "rulerrule-loading-error": "Kan de regel niet laden", + "toggle": { + "go-back-to-old-look": "", + "try-out-the-new-look": "" + }, "unknown-rule-type": "" }, "rule-list-errors": { @@ -2513,6 +2517,18 @@ "configuration-required": "", "refer-documentation-configure-authentication": "" }, + "fields": { + "allowed-groups-description": "", + "allowed-groups-description-oauth": "", + "api-url-description": "", + "team-ids-description": "", + "team-ids-description-oauth": "", + "team-ids-github": "", + "team-ids-other": "", + "teams-url-description": "", + "teams-url-description-oauth": "", + "use-pkce-description": "" + }, "provider-card": { "text-badge-enabled": "", "text-badge-not-enabled": "" @@ -2526,6 +2542,8 @@ "label-enabled": "", "reset-configuration": "", "reset-configuration-description": "", + "save": "", + "saving": "", "title-more-actions": "", "title-reset": "", "tooltip-more-actions": "" @@ -2671,6 +2689,9 @@ "canvas": { "not-found-display": { "not-found": "" + }, + "text-display": { + "double-click-to-set": "" } }, "carousel": { @@ -2947,7 +2968,9 @@ }, "annotation-settings-list": { "aria-label-delete": "", + "built-in": "", "data-source": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3060,6 +3083,7 @@ } }, "confirm-plugin-dashboard-save-modal": { + "body-plugin-dashboard": "", "cancel": "", "overwrite": "", "title-plugin-dashboard": "" @@ -3082,6 +3106,7 @@ } }, "dashboard-validation": { + "body-dashboard-failed-schema-validation": "", "title-checking-dashboard-validity": "", "title-dashboard-failed-schema-validation": "", "title-error-checking-dashboard-validity": "" @@ -3216,6 +3241,7 @@ }, "gen-aihistory": { "aria-label-send-custom-feedback": "", + "footer-text": "", "placeholder-tell-ai-what-to-do-next": "" }, "get-field-override-categories": { @@ -3306,9 +3332,13 @@ "make-editable": "" }, "minimalistic-pagination": { + "page-count": "", "tooltip-next": "", "tooltip-previous": "" }, + "new-dashboard-with-ds": { + "not-found": "" + }, "new-panel": { "configure-button": "", "configure-button-menu": "", @@ -3371,9 +3401,12 @@ "go-back-to-queries": "" }, "public-dashboard-not-available": { + "does-not-exist": "", + "paused": "", "try-again-later": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, "row-options-button": { @@ -3435,6 +3468,8 @@ "description": "", "label-copy-tags": "", "label-folder": "", + "save": "", + "saving": "", "title": "" }, "save-dashboard-button": { @@ -3451,19 +3486,30 @@ "label-details": "" }, "save-dashboard-error-proxy": { - "title-conflict": "" + "body-name-exists": "", + "body-version-mismatch": "", + "title-name-exists": "", + "title-version-mismatch": "" }, "save-dashboard-form": { "cancel": "", "label-current-range-dashboard-default": "", "label-current-variable-values-dashboard-default": "", "no-changes-to-save": "", - "placeholder-describe-changes": "" + "placeholder-describe-changes": "", + "save": "", + "saving": "" }, "save-provisioned-dashboard-form": { "cancel": "", + "cannot-be-saved": "", "copy-json-to-clipboard": "", - "save-json-to-file": "" + "file-path": "", + "save-json-to-file": "", + "see-docs": "" + }, + "share-public-dashboard-loader": { + "loading-configuration": "" }, "solo-panel": { "loading-initializing-dashboard": "", @@ -3642,6 +3688,9 @@ "title-delete": "" }, "transformation-picker": { + "info": "", + "info-graph-not-suitable": "", + "info-switch-to-table": "", "placeholder-search-for-transformation": "", "read-more": "", "title-transformations": "" @@ -3666,6 +3715,9 @@ "discard": "", "title-unsaved-changes": "" }, + "untheme-dashboard-row": { + "dashboard-datasource": "" + }, "unthemed-dashboard-row": { "aria-label-delete-row": "", "learn-more": "" @@ -3677,7 +3729,10 @@ "tags-expected-strings": "tags expected array van strings" }, "version-history-comparison": { - "label-view-json-diff": "" + "button-restore": "", + "label-view-json-diff": "", + "new-updated-by": "", + "old-updated-by": "" }, "version-history-table": { "aria-label-toggle-selection": "", @@ -3776,8 +3831,10 @@ "title-annotation-support-source": "" }, "annotation-settings-list": { + "built-in": "", "data-source": "", "delete-aria-label": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3838,6 +3895,8 @@ }, "data-source-variable-form": { "data-source-options": "", + "description-instance-name-filter": "", + "example-instance-name-filter": "", "selection-options": "" }, "description-label": { @@ -3898,7 +3957,13 @@ "title-plugin-dashboard": "", "title-someone-else-has-updated-this-dashboard": "", "would-still-dashboard": "" - } + }, + "save-and-overwrite": "" + }, + "library-viz-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_other": "" }, "name-already-exists-error": { "body-name-already-exists": "", @@ -3908,6 +3973,7 @@ "alert": { "title-errors-loading-rules": "" }, + "error-failed-to-load": "", "text-loading-rules": "", "title-dashboard-not-saved": "" }, @@ -3937,7 +4003,10 @@ "title-close": "" }, "provisioned-delete-modal": { + "cannot-be-deleted": "", + "file-path": "", "ok": "", + "see-docs": "", "title-cannot-delete-provisioned-dashboard": "" }, "provisioned-resource-delete-modal": { @@ -3950,13 +4019,21 @@ "query": "" }, "query-variable-editor-form": { + "description-examples": "", + "description-optional": "", "label-data-source": "", "query-options": "", "selection-options": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, + "save-button": { + "save": "", + "save-and-overwrite": "", + "saving": "" + }, "save-dashboard-as-form": { "aria-label-save-dashboard-description-field": "", "aria-label-save-dashboard-title-field": "", @@ -4009,17 +4086,21 @@ "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "affected-dashboards_one": "", + "affected-dashboards_other": "" }, "save-provisioned-dashboard-form": { "api-error": "Fout bij opslaan van dashboard", "api-success": "Dashboardwijzigingen opgeslagen", "cancel": "Annuleren", + "cannot-be-saved": "", "copy-json-message": "Als je directe toegang hebt tot het doel, kopieer je de JSON en plak je deze daar.", "copy-json-to-clipboard": "", "dashboard-comment-placeholder-describe-changes-optional": "Een opmerking toevoegen om je wijzigingen te beschrijven (optioneel)", "description-branch-name-in-git-hub": "Filiaalnaam in GitHub", "description-inside-repository": "Bestandspad in de repository (.json of .yaml)", + "file-path": "", "label-branch": "Vestiging", "label-comment": "Opmerking", "label-description": "Beschrijving", @@ -4030,6 +4111,7 @@ "save": "Opslaan", "save-json-to-file": "", "saving": "Aan het opslaan ...", + "see-docs": "", "title-required": "Dashboardtitel is vereist", "title-same-as-folder": "Dashboardnaam mag niet hetzelfde zijn als de mapnaam", "title-this-repository-is-read-only": "Deze repository is alleen-lezen", @@ -4048,6 +4130,9 @@ "share-button": { "aria-label-sharedropdownmenu": "" }, + "solo-panel-page": { + "loading": "" + }, "text-box-variable-form": { "placeholder-default-value-if-any": "", "text-options": "" @@ -4107,10 +4192,18 @@ "preview-of-values": "", "show-more": "" }, + "version-history": { + "comparison": { + "button-restore": "" + } + }, "version-history-comparison": { - "label-view-json-diff": "" + "label-view-json-diff": "", + "new-version-updated": "", + "old-version-updated": "" }, "version-history-header": { + "compare-versions": "", "latest": "", "tooltip-reset-version": "" }, @@ -4286,6 +4379,7 @@ }, "button-row": { "delete": "", + "save-and-test": "", "test": "" }, "cloud-info-box": { @@ -4309,6 +4403,9 @@ "data-source-picker": { "aria-label-select-a-data-source": "" }, + "data-source-plugin-config-page": { + "page-not-found": "" + }, "data-source-plugin-state": { "plugin-state": "" }, @@ -4445,8 +4542,12 @@ } }, "explore": { + "accordian-logs": { + "footer": "" + }, "accordian-references": { - "references": "" + "references": "", + "view-linked-span": "" }, "add-to-dashboard": "Toevoegen aan dashboard", "basic-extensions": { @@ -4455,6 +4556,7 @@ "clear": "", "confirm-navigation-modal": { "cancel": "", + "new-tab": "", "open": "", "open-in-new-tab": "" }, @@ -4475,9 +4577,14 @@ "add-transformation": "", "aria-label-delete-transformation": "", "aria-label-edit-transformation": "", + "body-correlation-details": "", + "expression": "", "label-description": "", + "label-description-header": "", "label-label": "", - "title-correlation-details": "" + "title-correlation-details": "", + "tooltip-transformations": "", + "transformations": "" }, "correlation-transformation-add-modal": { "add-transformation": "", @@ -4503,10 +4610,13 @@ "title-unsaved-changes-to-correlation": "" }, "draggable-manager-demo": { + "click-and-drag-gray-divider": "", "click-horizontally-somewhere-colored-below": "", + "drag-value": "", "draggable-manager-demo": "", "dragging-a-divider": "", - "dragging-a-sub-region": "" + "dragging-a-sub-region": "", + "value": "" }, "drilldownInfo": { "action": "Naar Grafana Drilldown", @@ -4537,6 +4647,10 @@ "outline": "", "tooltip-content-outline": "" }, + "feature-toggle-page": { + "description-explore-disabled": "", + "title-explore-disabled": "" + }, "get-matches-metadata": { "matches": "" }, @@ -4547,10 +4661,15 @@ }, "live-logs": { "clear-logs": "", - "exit-live-mode": "" + "exit-live-mode": "", + "last-line-received": "", + "pause": "", + "resume": "" }, "live-tail-button": { + "live": "", "pause-the-live-stream": "", + "paused": "", "start-live-stream-your-logs": "", "stop-and-exit-the-live-stream": "" }, @@ -4579,12 +4698,14 @@ "show-original-line": "" }, "logs-sample-panel": { + "label": "", "logs-sample-is-loading": "", "no-logs-sample-data": "", "open-in-split-view-button": { "open-logs-in-split-view": "" }, - "title-failed-sample-query": "" + "title-failed-sample-query": "", + "tooltip": "" }, "logs-table-available-fields": { "title-label-percentage": "" @@ -4593,7 +4714,9 @@ "no-fields": "" }, "logs-table-multi-select": { - "fields": "" + "fields": "", + "reset": "", + "selected-fields": "" }, "logs-table-nav-field": { "aria-label-drag-and-drop-icon": "", @@ -4612,13 +4735,20 @@ "title-showing-partial-data": "", "title-unable-to-show-log-volume": "" }, + "logs-volumne-panel-list": { + "body-no-logs-volume-available": "" + }, "make-datasource-setup": { "aria-label-query": "" }, "next": "", "next-prev-result": { "aria-label-next": "", - "aria-label-prev": "" + "aria-label-prev": "", + "depth": "", + "depth-span-filter-matches": "", + "services": "", + "services-span-filter-matches": "" }, "no-data-source-call-to-action": { "cta-element": { @@ -4634,6 +4764,7 @@ "aria-label-go-queryless": "" }, "raw-list-container": { + "item-count": "", "label-expand-results": "" }, "raw-list-item": { @@ -4649,7 +4780,6 @@ "newest-first": "Nieuwste eerst", "oldest-first": "Oudste eerst", "query-history": "Querygeschiedenis", - "query-library": "Querybibliotheek", "settings": "Instellingen", "starred": "Favorieten" }, @@ -4765,6 +4895,9 @@ }, "show-critical-path-only": "", "show-matches-only": "", + "span-bar": { + "tooltip-critical-path": "" + }, "span-bar-section": { "description-link": "", "title-span-bar": "" @@ -4791,6 +4924,7 @@ "aria-label-select-service-name-operator": "", "aria-label-select-span-name": "", "aria-label-select-span-name-operator": "", + "label-collapse": "", "label-duration": "", "label-service-name": "", "label-span-name": "", @@ -4834,6 +4968,9 @@ "tooltip-expand": "", "tooltip-expand-all": "" }, + "timeline-header-row": { + "title": "" + }, "toolbar": { "add-to-extensions": "Toevoegen", "add-to-queryless-extensions": "Zonder query's", @@ -4863,7 +5000,8 @@ "title-share-thoughts-about-tracing-grafana": "" }, "trace-page-header": { - "text-partial-trace": "" + "text-partial-trace": "", + "tooltip-url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "", @@ -4877,6 +5015,7 @@ "title-trace": "" }, "unconnected-node-graph-container": { + "count-warning": "", "title-node-graph": "" }, "unthemed-logs": { @@ -4930,11 +5069,20 @@ "classic-conditions": { "label-conditions": "" }, + "condition": { + "of": "", + "to": "", + "when": "" + }, "expression-query-editor": { "label-operation": "" }, "math": { - "available-math-functions": "" + "available-math-functions": "", + "run-math-operations": "", + "tooltip-footer": "", + "tooltip-title": "", + "tooltip-trigger": "" }, "reduce": { "label-function": "", @@ -5013,6 +5161,12 @@ }, "essentials": { "title-essentials": "" + }, + "progress-bar": { + "steps-status": "" + }, + "progress-status": { + "your-progress": "" } }, "grafana": { @@ -5404,8 +5558,14 @@ }, "inspector": { "inspect-data-tab": { + "loading": "", "no-data": "" }, + "inspect-error-tab": { + "error-status-message": "", + "error-status-no-message": "", + "error-trace-message": "" + }, "inspect-jsontab": { "apply": "", "support": "" @@ -5420,7 +5580,11 @@ }, "query-inspector": { "query-inspector": "", - "text-loading-query-inspector": "" + "text-loading-query-inspector": "", + "count-frames_one": "", + "count-frames_other": "", + "count-rows_one": "", + "count-rows_other": "" } }, "invites": { @@ -5432,6 +5596,11 @@ "name": "" }, "signup-invited-page": { + "complete-following": "", + "custom-has-invited-you": "", + "default-has-invited-you": "", + "greeting-custom": "", + "greeting-default": "", "label-email": "", "label-name": "", "label-password": "", @@ -5627,6 +5796,11 @@ "has-connected-dashboards": { "dashboard-name": "" }, + "library-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_other": "" + }, "library-panels-search": { "placeholder-search-by-name-or-description": "" }, @@ -5653,12 +5827,15 @@ "success": "Bibliotheekpaneel opgeslagen" }, "save-library-panel-modal": { + "affected-dashboards": "", "cancel": "", "dashboard-name": "", "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "num-affected_one": "", + "num-affected_other": "" } }, "link": { @@ -5683,6 +5860,7 @@ "live": { "dashboard-changed-modal": { "continue-editing": "", + "description": "", "discard-local-changes": "", "title-dashboard-changed": "" }, @@ -5761,6 +5939,9 @@ "collapse": "", "expand": "" }, + "log-labels-list": { + "log-line": "" + }, "log-line-menu": { "copy-link": "Link naar logboekregel kopiëren", "copy-log": "Logboekregel kopiëren", @@ -5850,6 +6031,8 @@ "aria-label-no-details": "" }, "un-themed-log-details-row": { + "filter-out": "", + "filter-out-query": "", "title-copy-value-to-clipboard": "", "toggle-field-button": { "tooltip-field-instead-message": "", @@ -5857,6 +6040,10 @@ }, "tooltip-adhoc-statistics": "", "tooltip-hide-adhoc-statistics": "" + }, + "un-themed-log-label-stats": { + "field-log-stats": "", + "label-log-stats": "" } }, "manage-dashbaords": { @@ -5878,6 +6065,7 @@ "options": "" }, "import-dashboard-overview-un-connected": { + "importing-from": "", "published-by": "", "updated-on": "" }, @@ -5937,6 +6125,9 @@ "button": "Migreer deze instantie naar de cloud", "header": "Laat ons je Grafana-stack beheren" }, + "dashboard-info": { + "dashboard": "" + }, "delete-migration-token-confirm": { "body": "Als je dit token al hebt gebruikt met een zelfbeheerde installatie, kan die installatie geen inhoud meer uploaden.", "confirm-button": "Token verwijderen", @@ -5952,6 +6143,7 @@ "title": "Verbinding met cloudstack verbreken" }, "folder-info": { + "folder": "", "unable-to-load-folder": "" }, "get-started": { @@ -6574,6 +6766,7 @@ "org": { "new-org-page": { "create": "", + "description": "", "label-organization-name": "", "placeholder-org-name": "" }, @@ -6582,6 +6775,9 @@ "label-organization-profile": "", "update-organization-name": "" }, + "select-org-page": { + "description": "" + }, "user-invite-form": { "back": "", "label-email-or-username": "", @@ -6589,7 +6785,11 @@ "label-send-invite-email": "", "placeholder-optional": "", "role": "", - "submit": "" + "submit": "", + "tooltip": "" + }, + "user-invite-page": { + "sub-title": "" } }, "org-picker": { @@ -6632,7 +6832,9 @@ "title-not-found": "" }, "panel-renderer": { + "failed-to-load-plugin": "", "loading-plugin-panel": "", + "no-panel-component": "", "no-panel-data": "" }, "panel-type-card": { @@ -6659,7 +6861,14 @@ "select-placeholder": "" }, "playlist": { + "playlist-table-rows": { + "aria-label-playlist-item": "", + "multiple-dashboards-found": "", + "no-dashboards-found": "", + "not-found": "" + }, "start-modal": { + "button-start": "", "description-customize-dashboard-elements-visibility": "", "description-panel-heights-adjusted-screen": "", "label-autofit": "", @@ -6719,6 +6928,11 @@ "message": "Geen afspeellijsten gevonden" } }, + "plugin": { + "plugin-details-deprecated-warning": { + "body-deprecated": "" + } + }, "plugins": { "app-root-page": { "access-denied": { @@ -6822,6 +7036,12 @@ "add-new-data-source": "", "title-button-disabled": "" }, + "install-controls": { + "install": "", + "installing": "", + "update": "", + "updating": "" + }, "install-controls-button": { "title-uninstall-modal": "" }, @@ -6840,6 +7060,7 @@ "label-severity": "" }, "not-found-plugin": { + "body-plugin-not-found": "", "title-plugin-not-found": "" }, "plugin-actions": { @@ -6854,12 +7075,17 @@ "tooltip-plugin-deprecated-longer-receives-updates": "" }, "plugin-details-body": { + "needs-service-account": "", "page-not-found": "" }, "plugin-details-deprecated-warning": { "title-deprecated": "" }, + "plugin-details-header-dependencies": { + "grafana-dependency": "" + }, "plugin-details-signature": { + "body-invalid-plugin-signature": "", "read-more-about-plugins-signing": "", "title-invalid-plugin-signature": "" }, @@ -6890,6 +7116,8 @@ "update-available": "" }, "plugin-usage": { + "body-missing-feature-toggle-panel-title-search": "", + "num-usages": "", "title-missing-feature-toggle-panel-title-search": "", "title-not-used-yet": "" }, @@ -6906,7 +7134,9 @@ }, "version-list": { "grafana-dependency": "", + "installed-version": "", "last-updated": "", + "latest-compatible-version": "", "no-version-history-was-found": "", "version": "" } @@ -6929,6 +7159,12 @@ }, "change-theme": "Thema wijzigen", "enable-kiosk-mode": "", + "feature-toggle-page": { + "profile-not-enabled": "" + }, + "feature-toggles-age": { + "enable-in-config": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -7073,6 +7309,8 @@ "repository-config-exists-configuration": "" }, "file-status-page": { + "save": "", + "saving": "", "title-error-loading-file": "" }, "files-view": { @@ -7103,7 +7341,6 @@ }, "getting-started": { "alert-temporary-outage": "", - "engaging-graphic": "", "modal-description-public-access": "", "modal-description-required-features": "", "modal-title-set-up-public-access": "", @@ -7199,13 +7436,16 @@ "checked": "", "finished": "", "health": "", + "healthy": "", "job-id": "", "last-ref": "", "messages": "", + "not-available": "", "pull-status": "", "resources": "", "started": "", "status": "", + "unhealthy": "", "view-folder": "", "webhook": "", "webhook-events": "", @@ -7496,31 +7736,41 @@ "hidden": "", "query-name-div-title-edit-query-name": "" }, + "query-error-alert": { + "trace-id": "" + }, "query-group": { "add-query": "", "expression": "", "title-data-source-help": "" }, "query-group-options-editor": { + "collapsed-interval": "", + "collapsed-max-data-points": "", "hide-time-info": "", "Query options-title-query-options": "", "relative-time": "", + "relative-time-tooltip": "", "render-cache-timeout-option": { "cache-timeout": "" }, "render-interval-option": { "interval": "", + "interval-tooltip": "", "min-interval": "", + "min-interval-tooltip": "", "time-range-max-data-points": "" }, "render-max-data-points-option": { "max-data-points": "", + "max-data-points-tooltip": "", "width-of-panel": "" }, "render-query-caching-ttloption": { "cache-ttl": "" }, - "time-shift": "" + "time-shift": "", + "time-shift-tooltip": "" }, "query-group-top-section": { "data-source": "", @@ -7702,6 +7952,9 @@ } }, "search": { + "action-row": { + "panel-type": "" + }, "actions": { "include-panels": "Panelen opnemen", "remove-datasource-filter": "Gegevensbron: {{datasource}}", @@ -7825,6 +8078,9 @@ "label-roles": "", "used-by": "" }, + "service-account-profile-row": { + "edit": "" + }, "service-account-role-row": { "aria-label-role": "" }, @@ -8349,6 +8605,7 @@ "generate-enum-values-from-data": "" }, "enum-mapping-row": { + "click-to-edit": "", "remove-enum-row-aria-label-delete-enum-row": "", "remove-enum-row-tooltip-delete": "" }, @@ -8403,10 +8660,13 @@ "label-format": "", "label-set-timezone": "", "label-time-field": "", + "tooltip-format": "", "tooltip-timezone-manually": "" }, "get-tooltips": { - "json-value": "" + "description": "", + "json-value": "", + "valid-paths": "" }, "group-by-field-configuration": { "placeholder-ignored": "", @@ -8623,6 +8883,7 @@ "message": "Geen gebruikers gevonden" }, "token-revoked-modal": { + "auto-revoked": "", "resume-message": "", "sign-in": "", "title-you-have-been-automatically-signed-out": "" @@ -8717,7 +8978,8 @@ "general": "", "placeholder-descriptive-text": "", "placeholder-label-name": "", - "placeholder-variable-name": "" + "placeholder-variable-name": "", + "run-query": "" }, "variable-editor-list": { "definition": "", diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index c36339b0dc6..cd71050b236 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -1,6 +1,5 @@ { "_comment": "Znaczenie zwrotów angielskich jest określane na podstawie kodu. Zwroty te należy aktualizować bezpośrednio w komponentach, a dodatkowe warianty dla liczby mnogiej – określić w tym pliku.", - "{{pageStart}} - {{pageEnd}} of {{numPages}}": "", "access-control": { "add-permission": { "built-in-aria-label": "", @@ -981,7 +980,8 @@ "expression-result": { "aria-label-nextpage": "", "aria-label-previouspage": "", - "no-data": "" + "no-data": "", + "page-counter": "" }, "federated-rule-warning": { "experimental": "", @@ -2024,6 +2024,10 @@ "title": "Reguły alertu" }, "rulerrule-loading-error": "Nie udało się załadować reguły", + "toggle": { + "go-back-to-old-look": "", + "try-out-the-new-look": "" + }, "unknown-rule-type": "" }, "rule-list-errors": { @@ -2539,6 +2543,18 @@ "configuration-required": "", "refer-documentation-configure-authentication": "" }, + "fields": { + "allowed-groups-description": "", + "allowed-groups-description-oauth": "", + "api-url-description": "", + "team-ids-description": "", + "team-ids-description-oauth": "", + "team-ids-github": "", + "team-ids-other": "", + "teams-url-description": "", + "teams-url-description-oauth": "", + "use-pkce-description": "" + }, "provider-card": { "text-badge-enabled": "", "text-badge-not-enabled": "" @@ -2552,6 +2568,8 @@ "label-enabled": "", "reset-configuration": "", "reset-configuration-description": "", + "save": "", + "saving": "", "title-more-actions": "", "title-reset": "", "tooltip-more-actions": "" @@ -2707,6 +2725,9 @@ "canvas": { "not-found-display": { "not-found": "" + }, + "text-display": { + "double-click-to-set": "" } }, "carousel": { @@ -2983,7 +3004,9 @@ }, "annotation-settings-list": { "aria-label-delete": "", + "built-in": "", "data-source": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3096,6 +3119,7 @@ } }, "confirm-plugin-dashboard-save-modal": { + "body-plugin-dashboard": "", "cancel": "", "overwrite": "", "title-plugin-dashboard": "" @@ -3118,6 +3142,7 @@ } }, "dashboard-validation": { + "body-dashboard-failed-schema-validation": "", "title-checking-dashboard-validity": "", "title-dashboard-failed-schema-validation": "", "title-error-checking-dashboard-validity": "" @@ -3252,6 +3277,7 @@ }, "gen-aihistory": { "aria-label-send-custom-feedback": "", + "footer-text": "", "placeholder-tell-ai-what-to-do-next": "" }, "get-field-override-categories": { @@ -3342,9 +3368,13 @@ "make-editable": "" }, "minimalistic-pagination": { + "page-count": "", "tooltip-next": "", "tooltip-previous": "" }, + "new-dashboard-with-ds": { + "not-found": "" + }, "new-panel": { "configure-button": "", "configure-button-menu": "", @@ -3409,9 +3439,12 @@ "go-back-to-queries": "" }, "public-dashboard-not-available": { + "does-not-exist": "", + "paused": "", "try-again-later": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, "row-options-button": { @@ -3473,6 +3506,8 @@ "description": "", "label-copy-tags": "", "label-folder": "", + "save": "", + "saving": "", "title": "" }, "save-dashboard-button": { @@ -3489,19 +3524,30 @@ "label-details": "" }, "save-dashboard-error-proxy": { - "title-conflict": "" + "body-name-exists": "", + "body-version-mismatch": "", + "title-name-exists": "", + "title-version-mismatch": "" }, "save-dashboard-form": { "cancel": "", "label-current-range-dashboard-default": "", "label-current-variable-values-dashboard-default": "", "no-changes-to-save": "", - "placeholder-describe-changes": "" + "placeholder-describe-changes": "", + "save": "", + "saving": "" }, "save-provisioned-dashboard-form": { "cancel": "", + "cannot-be-saved": "", "copy-json-to-clipboard": "", - "save-json-to-file": "" + "file-path": "", + "save-json-to-file": "", + "see-docs": "" + }, + "share-public-dashboard-loader": { + "loading-configuration": "" }, "solo-panel": { "loading-initializing-dashboard": "", @@ -3680,6 +3726,9 @@ "title-delete": "" }, "transformation-picker": { + "info": "", + "info-graph-not-suitable": "", + "info-switch-to-table": "", "placeholder-search-for-transformation": "", "read-more": "", "title-transformations": "" @@ -3704,6 +3753,9 @@ "discard": "", "title-unsaved-changes": "" }, + "untheme-dashboard-row": { + "dashboard-datasource": "" + }, "unthemed-dashboard-row": { "aria-label-delete-row": "", "learn-more": "" @@ -3715,7 +3767,10 @@ "tags-expected-strings": "znaczniki oczekiwały tablicy łańcuchów" }, "version-history-comparison": { - "label-view-json-diff": "" + "button-restore": "", + "label-view-json-diff": "", + "new-updated-by": "", + "old-updated-by": "" }, "version-history-table": { "aria-label-toggle-selection": "", @@ -3814,8 +3869,10 @@ "title-annotation-support-source": "" }, "annotation-settings-list": { + "built-in": "", "data-source": "", "delete-aria-label": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3876,6 +3933,8 @@ }, "data-source-variable-form": { "data-source-options": "", + "description-instance-name-filter": "", + "example-instance-name-filter": "", "selection-options": "" }, "description-label": { @@ -3936,7 +3995,15 @@ "title-plugin-dashboard": "", "title-someone-else-has-updated-this-dashboard": "", "would-still-dashboard": "" - } + }, + "save-and-overwrite": "" + }, + "library-viz-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_few": "", + "usage-count_many": "", + "usage-count_other": "" }, "name-already-exists-error": { "body-name-already-exists": "", @@ -3946,6 +4013,7 @@ "alert": { "title-errors-loading-rules": "" }, + "error-failed-to-load": "", "text-loading-rules": "", "title-dashboard-not-saved": "" }, @@ -3975,7 +4043,10 @@ "title-close": "" }, "provisioned-delete-modal": { + "cannot-be-deleted": "", + "file-path": "", "ok": "", + "see-docs": "", "title-cannot-delete-provisioned-dashboard": "" }, "provisioned-resource-delete-modal": { @@ -3988,13 +4059,21 @@ "query": "" }, "query-variable-editor-form": { + "description-examples": "", + "description-optional": "", "label-data-source": "", "query-options": "", "selection-options": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, + "save-button": { + "save": "", + "save-and-overwrite": "", + "saving": "" + }, "save-dashboard-as-form": { "aria-label-save-dashboard-description-field": "", "aria-label-save-dashboard-title-field": "", @@ -4047,17 +4126,23 @@ "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "affected-dashboards_one": "", + "affected-dashboards_few": "", + "affected-dashboards_many": "", + "affected-dashboards_other": "" }, "save-provisioned-dashboard-form": { "api-error": "Błąd podczas zapisywania pulpitu", "api-success": "Zapisano zmiany w pulpicie", "cancel": "Anuluj", + "cannot-be-saved": "", "copy-json-message": "Jeśli masz bezpośredni dostęp do celu, skopiuj kod JSON i wklej go tam.", "copy-json-to-clipboard": "", "dashboard-comment-placeholder-describe-changes-optional": "Dodaj notatkę, aby opisać zmiany (opcjonalnie)", "description-branch-name-in-git-hub": "Nazwa gałęzi w GitHub", "description-inside-repository": "Ścieżka pliku wewnątrz repozytorium (.json lub .yaml)", + "file-path": "", "label-branch": "Gałąź", "label-comment": "Uwagi", "label-description": "Opis", @@ -4068,6 +4153,7 @@ "save": "Zapisz", "save-json-to-file": "", "saving": "Zapisywanie…", + "see-docs": "", "title-required": "Wymagany tytuł pulpitu", "title-same-as-folder": "Nazwa pulpitu nie może być taka sama jak nazwa folderu", "title-this-repository-is-read-only": "To repozytorium jest tylko do odczytu", @@ -4086,6 +4172,9 @@ "share-button": { "aria-label-sharedropdownmenu": "" }, + "solo-panel-page": { + "loading": "" + }, "text-box-variable-form": { "placeholder-default-value-if-any": "", "text-options": "" @@ -4145,10 +4234,18 @@ "preview-of-values": "", "show-more": "" }, + "version-history": { + "comparison": { + "button-restore": "" + } + }, "version-history-comparison": { - "label-view-json-diff": "" + "label-view-json-diff": "", + "new-version-updated": "", + "old-version-updated": "" }, "version-history-header": { + "compare-versions": "", "latest": "", "tooltip-reset-version": "" }, @@ -4324,6 +4421,7 @@ }, "button-row": { "delete": "", + "save-and-test": "", "test": "" }, "cloud-info-box": { @@ -4347,6 +4445,9 @@ "data-source-picker": { "aria-label-select-a-data-source": "" }, + "data-source-plugin-config-page": { + "page-not-found": "" + }, "data-source-plugin-state": { "plugin-state": "" }, @@ -4483,8 +4584,12 @@ } }, "explore": { + "accordian-logs": { + "footer": "" + }, "accordian-references": { - "references": "" + "references": "", + "view-linked-span": "" }, "add-to-dashboard": "Dodaj do pulpitu", "basic-extensions": { @@ -4493,6 +4598,7 @@ "clear": "", "confirm-navigation-modal": { "cancel": "", + "new-tab": "", "open": "", "open-in-new-tab": "" }, @@ -4513,9 +4619,14 @@ "add-transformation": "", "aria-label-delete-transformation": "", "aria-label-edit-transformation": "", + "body-correlation-details": "", + "expression": "", "label-description": "", + "label-description-header": "", "label-label": "", - "title-correlation-details": "" + "title-correlation-details": "", + "tooltip-transformations": "", + "transformations": "" }, "correlation-transformation-add-modal": { "add-transformation": "", @@ -4541,10 +4652,13 @@ "title-unsaved-changes-to-correlation": "" }, "draggable-manager-demo": { + "click-and-drag-gray-divider": "", "click-horizontally-somewhere-colored-below": "", + "drag-value": "", "draggable-manager-demo": "", "dragging-a-divider": "", - "dragging-a-sub-region": "" + "dragging-a-sub-region": "", + "value": "" }, "drilldownInfo": { "action": "Przejdź do narzędzia Grafana Drilldown", @@ -4575,6 +4689,10 @@ "outline": "", "tooltip-content-outline": "" }, + "feature-toggle-page": { + "description-explore-disabled": "", + "title-explore-disabled": "" + }, "get-matches-metadata": { "matches": "" }, @@ -4585,10 +4703,15 @@ }, "live-logs": { "clear-logs": "", - "exit-live-mode": "" + "exit-live-mode": "", + "last-line-received": "", + "pause": "", + "resume": "" }, "live-tail-button": { + "live": "", "pause-the-live-stream": "", + "paused": "", "start-live-stream-your-logs": "", "stop-and-exit-the-live-stream": "" }, @@ -4617,12 +4740,14 @@ "show-original-line": "" }, "logs-sample-panel": { + "label": "", "logs-sample-is-loading": "", "no-logs-sample-data": "", "open-in-split-view-button": { "open-logs-in-split-view": "" }, - "title-failed-sample-query": "" + "title-failed-sample-query": "", + "tooltip": "" }, "logs-table-available-fields": { "title-label-percentage": "" @@ -4631,7 +4756,9 @@ "no-fields": "" }, "logs-table-multi-select": { - "fields": "" + "fields": "", + "reset": "", + "selected-fields": "" }, "logs-table-nav-field": { "aria-label-drag-and-drop-icon": "", @@ -4650,13 +4777,20 @@ "title-showing-partial-data": "", "title-unable-to-show-log-volume": "" }, + "logs-volumne-panel-list": { + "body-no-logs-volume-available": "" + }, "make-datasource-setup": { "aria-label-query": "" }, "next": "", "next-prev-result": { "aria-label-next": "", - "aria-label-prev": "" + "aria-label-prev": "", + "depth": "", + "depth-span-filter-matches": "", + "services": "", + "services-span-filter-matches": "" }, "no-data-source-call-to-action": { "cta-element": { @@ -4672,6 +4806,7 @@ "aria-label-go-queryless": "" }, "raw-list-container": { + "item-count": "", "label-expand-results": "" }, "raw-list-item": { @@ -4687,7 +4822,6 @@ "newest-first": "Od najnowszych", "oldest-first": "Od najstarszych", "query-history": "Historia zapytań", - "query-library": "Biblioteka zapytań", "settings": "Ustawienia", "starred": "Oznaczone gwiazdką" }, @@ -4803,6 +4937,9 @@ }, "show-critical-path-only": "", "show-matches-only": "", + "span-bar": { + "tooltip-critical-path": "" + }, "span-bar-section": { "description-link": "", "title-span-bar": "" @@ -4829,6 +4966,7 @@ "aria-label-select-service-name-operator": "", "aria-label-select-span-name": "", "aria-label-select-span-name-operator": "", + "label-collapse": "", "label-duration": "", "label-service-name": "", "label-span-name": "", @@ -4872,6 +5010,9 @@ "tooltip-expand": "", "tooltip-expand-all": "" }, + "timeline-header-row": { + "title": "" + }, "toolbar": { "add-to-extensions": "Dodaj", "add-to-queryless-extensions": "Bez zapytań", @@ -4901,7 +5042,8 @@ "title-share-thoughts-about-tracing-grafana": "" }, "trace-page-header": { - "text-partial-trace": "" + "text-partial-trace": "", + "tooltip-url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "", @@ -4915,6 +5057,7 @@ "title-trace": "" }, "unconnected-node-graph-container": { + "count-warning": "", "title-node-graph": "" }, "unthemed-logs": { @@ -4968,11 +5111,20 @@ "classic-conditions": { "label-conditions": "" }, + "condition": { + "of": "", + "to": "", + "when": "" + }, "expression-query-editor": { "label-operation": "" }, "math": { - "available-math-functions": "" + "available-math-functions": "", + "run-math-operations": "", + "tooltip-footer": "", + "tooltip-title": "", + "tooltip-trigger": "" }, "reduce": { "label-function": "", @@ -5051,6 +5203,12 @@ }, "essentials": { "title-essentials": "" + }, + "progress-bar": { + "steps-status": "" + }, + "progress-status": { + "your-progress": "" } }, "grafana": { @@ -5442,8 +5600,14 @@ }, "inspector": { "inspect-data-tab": { + "loading": "", "no-data": "" }, + "inspect-error-tab": { + "error-status-message": "", + "error-status-no-message": "", + "error-trace-message": "" + }, "inspect-jsontab": { "apply": "", "support": "" @@ -5458,7 +5622,15 @@ }, "query-inspector": { "query-inspector": "", - "text-loading-query-inspector": "" + "text-loading-query-inspector": "", + "count-frames_one": "", + "count-frames_few": "", + "count-frames_many": "", + "count-frames_other": "", + "count-rows_one": "", + "count-rows_few": "", + "count-rows_many": "", + "count-rows_other": "" } }, "invites": { @@ -5470,6 +5642,11 @@ "name": "" }, "signup-invited-page": { + "complete-following": "", + "custom-has-invited-you": "", + "default-has-invited-you": "", + "greeting-custom": "", + "greeting-default": "", "label-email": "", "label-name": "", "label-password": "", @@ -5665,6 +5842,13 @@ "has-connected-dashboards": { "dashboard-name": "" }, + "library-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_few": "", + "usage-count_many": "", + "usage-count_other": "" + }, "library-panels-search": { "placeholder-search-by-name-or-description": "" }, @@ -5693,12 +5877,17 @@ "success": "Zapisano panel biblioteki" }, "save-library-panel-modal": { + "affected-dashboards": "", "cancel": "", "dashboard-name": "", "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "num-affected_one": "", + "num-affected_few": "", + "num-affected_many": "", + "num-affected_other": "" } }, "link": { @@ -5723,6 +5912,7 @@ "live": { "dashboard-changed-modal": { "continue-editing": "", + "description": "", "discard-local-changes": "", "title-dashboard-changed": "" }, @@ -5801,6 +5991,9 @@ "collapse": "", "expand": "" }, + "log-labels-list": { + "log-line": "" + }, "log-line-menu": { "copy-link": "Kopiuj link do wiersza logu", "copy-log": "Kopiuj wiersz logu", @@ -5890,6 +6083,8 @@ "aria-label-no-details": "" }, "un-themed-log-details-row": { + "filter-out": "", + "filter-out-query": "", "title-copy-value-to-clipboard": "", "toggle-field-button": { "tooltip-field-instead-message": "", @@ -5897,6 +6092,10 @@ }, "tooltip-adhoc-statistics": "", "tooltip-hide-adhoc-statistics": "" + }, + "un-themed-log-label-stats": { + "field-log-stats": "", + "label-log-stats": "" } }, "manage-dashbaords": { @@ -5918,6 +6117,7 @@ "options": "" }, "import-dashboard-overview-un-connected": { + "importing-from": "", "published-by": "", "updated-on": "" }, @@ -5977,6 +6177,9 @@ "button": "Przenieś tę instancję do chmury", "header": "Pozwól nam zarządzać Twoim stosem Grafana" }, + "dashboard-info": { + "dashboard": "" + }, "delete-migration-token-confirm": { "body": "Jeśli tego tokena użyto już z samodzielnie zarządzaną instalacją, ta instalacja nie będzie już mogła przesyłać treści.", "confirm-button": "Usuń token", @@ -5992,6 +6195,7 @@ "title": "Odłącz od stosu w chmurze" }, "folder-info": { + "folder": "", "unable-to-load-folder": "" }, "get-started": { @@ -6614,6 +6818,7 @@ "org": { "new-org-page": { "create": "", + "description": "", "label-organization-name": "", "placeholder-org-name": "" }, @@ -6622,6 +6827,9 @@ "label-organization-profile": "", "update-organization-name": "" }, + "select-org-page": { + "description": "" + }, "user-invite-form": { "back": "", "label-email-or-username": "", @@ -6629,7 +6837,11 @@ "label-send-invite-email": "", "placeholder-optional": "", "role": "", - "submit": "" + "submit": "", + "tooltip": "" + }, + "user-invite-page": { + "sub-title": "" } }, "org-picker": { @@ -6672,7 +6884,9 @@ "title-not-found": "" }, "panel-renderer": { + "failed-to-load-plugin": "", "loading-plugin-panel": "", + "no-panel-component": "", "no-panel-data": "" }, "panel-type-card": { @@ -6699,7 +6913,14 @@ "select-placeholder": "" }, "playlist": { + "playlist-table-rows": { + "aria-label-playlist-item": "", + "multiple-dashboards-found": "", + "no-dashboards-found": "", + "not-found": "" + }, "start-modal": { + "button-start": "", "description-customize-dashboard-elements-visibility": "", "description-panel-heights-adjusted-screen": "", "label-autofit": "", @@ -6759,6 +6980,11 @@ "message": "Nie odnaleziono list autoodtwarzania" } }, + "plugin": { + "plugin-details-deprecated-warning": { + "body-deprecated": "" + } + }, "plugins": { "app-root-page": { "access-denied": { @@ -6862,6 +7088,12 @@ "add-new-data-source": "", "title-button-disabled": "" }, + "install-controls": { + "install": "", + "installing": "", + "update": "", + "updating": "" + }, "install-controls-button": { "title-uninstall-modal": "" }, @@ -6880,6 +7112,7 @@ "label-severity": "" }, "not-found-plugin": { + "body-plugin-not-found": "", "title-plugin-not-found": "" }, "plugin-actions": { @@ -6894,12 +7127,17 @@ "tooltip-plugin-deprecated-longer-receives-updates": "" }, "plugin-details-body": { + "needs-service-account": "", "page-not-found": "" }, "plugin-details-deprecated-warning": { "title-deprecated": "" }, + "plugin-details-header-dependencies": { + "grafana-dependency": "" + }, "plugin-details-signature": { + "body-invalid-plugin-signature": "", "read-more-about-plugins-signing": "", "title-invalid-plugin-signature": "" }, @@ -6930,6 +7168,8 @@ "update-available": "" }, "plugin-usage": { + "body-missing-feature-toggle-panel-title-search": "", + "num-usages": "", "title-missing-feature-toggle-panel-title-search": "", "title-not-used-yet": "" }, @@ -6946,7 +7186,9 @@ }, "version-list": { "grafana-dependency": "", + "installed-version": "", "last-updated": "", + "latest-compatible-version": "", "no-version-history-was-found": "", "version": "" } @@ -6969,6 +7211,12 @@ }, "change-theme": "Zmień motyw", "enable-kiosk-mode": "", + "feature-toggle-page": { + "profile-not-enabled": "" + }, + "feature-toggles-age": { + "enable-in-config": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -7115,6 +7363,8 @@ "repository-config-exists-configuration": "" }, "file-status-page": { + "save": "", + "saving": "", "title-error-loading-file": "" }, "files-view": { @@ -7145,7 +7395,6 @@ }, "getting-started": { "alert-temporary-outage": "", - "engaging-graphic": "", "modal-description-public-access": "", "modal-description-required-features": "", "modal-title-set-up-public-access": "", @@ -7241,13 +7490,16 @@ "checked": "", "finished": "", "health": "", + "healthy": "", "job-id": "", "last-ref": "", "messages": "", + "not-available": "", "pull-status": "", "resources": "", "started": "", "status": "", + "unhealthy": "", "view-folder": "", "webhook": "", "webhook-events": "", @@ -7540,31 +7792,41 @@ "hidden": "", "query-name-div-title-edit-query-name": "" }, + "query-error-alert": { + "trace-id": "" + }, "query-group": { "add-query": "", "expression": "", "title-data-source-help": "" }, "query-group-options-editor": { + "collapsed-interval": "", + "collapsed-max-data-points": "", "hide-time-info": "", "Query options-title-query-options": "", "relative-time": "", + "relative-time-tooltip": "", "render-cache-timeout-option": { "cache-timeout": "" }, "render-interval-option": { "interval": "", + "interval-tooltip": "", "min-interval": "", + "min-interval-tooltip": "", "time-range-max-data-points": "" }, "render-max-data-points-option": { "max-data-points": "", + "max-data-points-tooltip": "", "width-of-panel": "" }, "render-query-caching-ttloption": { "cache-ttl": "" }, - "time-shift": "" + "time-shift": "", + "time-shift-tooltip": "" }, "query-group-top-section": { "data-source": "", @@ -7752,6 +8014,9 @@ } }, "search": { + "action-row": { + "panel-type": "" + }, "actions": { "include-panels": "Uwzględnij panele", "remove-datasource-filter": "Źródło danych: {{datasource}}", @@ -7875,6 +8140,9 @@ "label-roles": "", "used-by": "" }, + "service-account-profile-row": { + "edit": "" + }, "service-account-role-row": { "aria-label-role": "" }, @@ -8399,6 +8667,7 @@ "generate-enum-values-from-data": "" }, "enum-mapping-row": { + "click-to-edit": "", "remove-enum-row-aria-label-delete-enum-row": "", "remove-enum-row-tooltip-delete": "" }, @@ -8453,10 +8722,13 @@ "label-format": "", "label-set-timezone": "", "label-time-field": "", + "tooltip-format": "", "tooltip-timezone-manually": "" }, "get-tooltips": { - "json-value": "" + "description": "", + "json-value": "", + "valid-paths": "" }, "group-by-field-configuration": { "placeholder-ignored": "", @@ -8673,6 +8945,7 @@ "message": "Nie znaleziono użytkowników" }, "token-revoked-modal": { + "auto-revoked": "", "resume-message": "", "sign-in": "", "title-you-have-been-automatically-signed-out": "" @@ -8767,7 +9040,8 @@ "general": "", "placeholder-descriptive-text": "", "placeholder-label-name": "", - "placeholder-variable-name": "" + "placeholder-variable-name": "", + "run-query": "" }, "variable-editor-list": { "definition": "", diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index 8cf65d71240..fa048892338 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -1,6 +1,5 @@ { "_comment": "O código é a fonte da verdade para frases em inglês. Elas devem ser atualizadas diretamente nos componentes e os plurais adicionais devem ser especificados neste arquivo.", - "{{pageStart}} - {{pageEnd}} of {{numPages}}": "", "access-control": { "add-permission": { "built-in-aria-label": "", @@ -969,7 +968,8 @@ "expression-result": { "aria-label-nextpage": "", "aria-label-previouspage": "", - "no-data": "" + "no-data": "", + "page-counter": "" }, "federated-rule-warning": { "experimental": "", @@ -2004,6 +2004,10 @@ "title": "" }, "rulerrule-loading-error": "Falha ao carregar a regra", + "toggle": { + "go-back-to-old-look": "", + "try-out-the-new-look": "" + }, "unknown-rule-type": "" }, "rule-list-errors": { @@ -2513,6 +2517,18 @@ "configuration-required": "", "refer-documentation-configure-authentication": "" }, + "fields": { + "allowed-groups-description": "", + "allowed-groups-description-oauth": "", + "api-url-description": "", + "team-ids-description": "", + "team-ids-description-oauth": "", + "team-ids-github": "", + "team-ids-other": "", + "teams-url-description": "", + "teams-url-description-oauth": "", + "use-pkce-description": "" + }, "provider-card": { "text-badge-enabled": "", "text-badge-not-enabled": "" @@ -2526,6 +2542,8 @@ "label-enabled": "", "reset-configuration": "", "reset-configuration-description": "", + "save": "", + "saving": "", "title-more-actions": "", "title-reset": "", "tooltip-more-actions": "" @@ -2671,6 +2689,9 @@ "canvas": { "not-found-display": { "not-found": "" + }, + "text-display": { + "double-click-to-set": "" } }, "carousel": { @@ -2947,7 +2968,9 @@ }, "annotation-settings-list": { "aria-label-delete": "", + "built-in": "", "data-source": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3060,6 +3083,7 @@ } }, "confirm-plugin-dashboard-save-modal": { + "body-plugin-dashboard": "", "cancel": "", "overwrite": "", "title-plugin-dashboard": "" @@ -3082,6 +3106,7 @@ } }, "dashboard-validation": { + "body-dashboard-failed-schema-validation": "", "title-checking-dashboard-validity": "", "title-dashboard-failed-schema-validation": "", "title-error-checking-dashboard-validity": "" @@ -3216,6 +3241,7 @@ }, "gen-aihistory": { "aria-label-send-custom-feedback": "", + "footer-text": "", "placeholder-tell-ai-what-to-do-next": "" }, "get-field-override-categories": { @@ -3306,9 +3332,13 @@ "make-editable": "" }, "minimalistic-pagination": { + "page-count": "", "tooltip-next": "", "tooltip-previous": "" }, + "new-dashboard-with-ds": { + "not-found": "" + }, "new-panel": { "configure-button": "", "configure-button-menu": "", @@ -3371,9 +3401,12 @@ "go-back-to-queries": "" }, "public-dashboard-not-available": { + "does-not-exist": "", + "paused": "", "try-again-later": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, "row-options-button": { @@ -3435,6 +3468,8 @@ "description": "", "label-copy-tags": "", "label-folder": "", + "save": "", + "saving": "", "title": "" }, "save-dashboard-button": { @@ -3451,19 +3486,30 @@ "label-details": "" }, "save-dashboard-error-proxy": { - "title-conflict": "" + "body-name-exists": "", + "body-version-mismatch": "", + "title-name-exists": "", + "title-version-mismatch": "" }, "save-dashboard-form": { "cancel": "", "label-current-range-dashboard-default": "", "label-current-variable-values-dashboard-default": "", "no-changes-to-save": "", - "placeholder-describe-changes": "" + "placeholder-describe-changes": "", + "save": "", + "saving": "" }, "save-provisioned-dashboard-form": { "cancel": "", + "cannot-be-saved": "", "copy-json-to-clipboard": "", - "save-json-to-file": "" + "file-path": "", + "save-json-to-file": "", + "see-docs": "" + }, + "share-public-dashboard-loader": { + "loading-configuration": "" }, "solo-panel": { "loading-initializing-dashboard": "", @@ -3642,6 +3688,9 @@ "title-delete": "" }, "transformation-picker": { + "info": "", + "info-graph-not-suitable": "", + "info-switch-to-table": "", "placeholder-search-for-transformation": "", "read-more": "", "title-transformations": "" @@ -3666,6 +3715,9 @@ "discard": "", "title-unsaved-changes": "" }, + "untheme-dashboard-row": { + "dashboard-datasource": "" + }, "unthemed-dashboard-row": { "aria-label-delete-row": "", "learn-more": "" @@ -3677,7 +3729,10 @@ "tags-expected-strings": "matriz de strings de tags esperada" }, "version-history-comparison": { - "label-view-json-diff": "" + "button-restore": "", + "label-view-json-diff": "", + "new-updated-by": "", + "old-updated-by": "" }, "version-history-table": { "aria-label-toggle-selection": "", @@ -3776,8 +3831,10 @@ "title-annotation-support-source": "" }, "annotation-settings-list": { + "built-in": "", "data-source": "", "delete-aria-label": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3838,6 +3895,8 @@ }, "data-source-variable-form": { "data-source-options": "", + "description-instance-name-filter": "", + "example-instance-name-filter": "", "selection-options": "" }, "description-label": { @@ -3898,7 +3957,13 @@ "title-plugin-dashboard": "", "title-someone-else-has-updated-this-dashboard": "", "would-still-dashboard": "" - } + }, + "save-and-overwrite": "" + }, + "library-viz-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_other": "" }, "name-already-exists-error": { "body-name-already-exists": "", @@ -3908,6 +3973,7 @@ "alert": { "title-errors-loading-rules": "" }, + "error-failed-to-load": "", "text-loading-rules": "", "title-dashboard-not-saved": "" }, @@ -3937,7 +4003,10 @@ "title-close": "" }, "provisioned-delete-modal": { + "cannot-be-deleted": "", + "file-path": "", "ok": "", + "see-docs": "", "title-cannot-delete-provisioned-dashboard": "" }, "provisioned-resource-delete-modal": { @@ -3950,13 +4019,21 @@ "query": "" }, "query-variable-editor-form": { + "description-examples": "", + "description-optional": "", "label-data-source": "", "query-options": "", "selection-options": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, + "save-button": { + "save": "", + "save-and-overwrite": "", + "saving": "" + }, "save-dashboard-as-form": { "aria-label-save-dashboard-description-field": "", "aria-label-save-dashboard-title-field": "", @@ -4009,17 +4086,21 @@ "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "affected-dashboards_one": "", + "affected-dashboards_other": "" }, "save-provisioned-dashboard-form": { "api-error": "Erro ao salvar o painel de controle", "api-success": "Alterações no painel de controle salvas", "cancel": "", + "cannot-be-saved": "", "copy-json-message": "Se você tiver acesso direto ao destino, copie o JSON e cole-o lá.", "copy-json-to-clipboard": "", "dashboard-comment-placeholder-describe-changes-optional": "Adicione uma nota para descrever suas alterações (opcional)", "description-branch-name-in-git-hub": "Nome do branch no GitHub", "description-inside-repository": "Caminho do arquivo dentro do repositório (.json ou .yaml)", + "file-path": "", "label-branch": "Branch", "label-comment": "Comentário", "label-description": "", @@ -4030,6 +4111,7 @@ "save": "", "save-json-to-file": "", "saving": "Salvando...", + "see-docs": "", "title-required": "Título do painel de controle é um campo obrigatório", "title-same-as-folder": "O nome do painel de controle não pode ser igual ao nome da pasta", "title-this-repository-is-read-only": "Este repositório é somente leitura", @@ -4048,6 +4130,9 @@ "share-button": { "aria-label-sharedropdownmenu": "" }, + "solo-panel-page": { + "loading": "" + }, "text-box-variable-form": { "placeholder-default-value-if-any": "", "text-options": "" @@ -4107,10 +4192,18 @@ "preview-of-values": "", "show-more": "" }, + "version-history": { + "comparison": { + "button-restore": "" + } + }, "version-history-comparison": { - "label-view-json-diff": "" + "label-view-json-diff": "", + "new-version-updated": "", + "old-version-updated": "" }, "version-history-header": { + "compare-versions": "", "latest": "", "tooltip-reset-version": "" }, @@ -4286,6 +4379,7 @@ }, "button-row": { "delete": "", + "save-and-test": "", "test": "" }, "cloud-info-box": { @@ -4309,6 +4403,9 @@ "data-source-picker": { "aria-label-select-a-data-source": "" }, + "data-source-plugin-config-page": { + "page-not-found": "" + }, "data-source-plugin-state": { "plugin-state": "" }, @@ -4445,8 +4542,12 @@ } }, "explore": { + "accordian-logs": { + "footer": "" + }, "accordian-references": { - "references": "" + "references": "", + "view-linked-span": "" }, "add-to-dashboard": "Adicionar ao painel de controle", "basic-extensions": { @@ -4455,6 +4556,7 @@ "clear": "", "confirm-navigation-modal": { "cancel": "", + "new-tab": "", "open": "", "open-in-new-tab": "" }, @@ -4475,9 +4577,14 @@ "add-transformation": "", "aria-label-delete-transformation": "", "aria-label-edit-transformation": "", + "body-correlation-details": "", + "expression": "", "label-description": "", + "label-description-header": "", "label-label": "", - "title-correlation-details": "" + "title-correlation-details": "", + "tooltip-transformations": "", + "transformations": "" }, "correlation-transformation-add-modal": { "add-transformation": "", @@ -4503,10 +4610,13 @@ "title-unsaved-changes-to-correlation": "" }, "draggable-manager-demo": { + "click-and-drag-gray-divider": "", "click-horizontally-somewhere-colored-below": "", + "drag-value": "", "draggable-manager-demo": "", "dragging-a-divider": "", - "dragging-a-sub-region": "" + "dragging-a-sub-region": "", + "value": "" }, "drilldownInfo": { "action": "Ir para Grafana Aprofundar", @@ -4537,6 +4647,10 @@ "outline": "", "tooltip-content-outline": "" }, + "feature-toggle-page": { + "description-explore-disabled": "", + "title-explore-disabled": "" + }, "get-matches-metadata": { "matches": "" }, @@ -4547,10 +4661,15 @@ }, "live-logs": { "clear-logs": "", - "exit-live-mode": "" + "exit-live-mode": "", + "last-line-received": "", + "pause": "", + "resume": "" }, "live-tail-button": { + "live": "", "pause-the-live-stream": "", + "paused": "", "start-live-stream-your-logs": "", "stop-and-exit-the-live-stream": "" }, @@ -4579,12 +4698,14 @@ "show-original-line": "" }, "logs-sample-panel": { + "label": "", "logs-sample-is-loading": "", "no-logs-sample-data": "", "open-in-split-view-button": { "open-logs-in-split-view": "" }, - "title-failed-sample-query": "" + "title-failed-sample-query": "", + "tooltip": "" }, "logs-table-available-fields": { "title-label-percentage": "" @@ -4593,7 +4714,9 @@ "no-fields": "" }, "logs-table-multi-select": { - "fields": "" + "fields": "", + "reset": "", + "selected-fields": "" }, "logs-table-nav-field": { "aria-label-drag-and-drop-icon": "", @@ -4612,13 +4735,20 @@ "title-showing-partial-data": "", "title-unable-to-show-log-volume": "" }, + "logs-volumne-panel-list": { + "body-no-logs-volume-available": "" + }, "make-datasource-setup": { "aria-label-query": "" }, "next": "", "next-prev-result": { "aria-label-next": "", - "aria-label-prev": "" + "aria-label-prev": "", + "depth": "", + "depth-span-filter-matches": "", + "services": "", + "services-span-filter-matches": "" }, "no-data-source-call-to-action": { "cta-element": { @@ -4634,6 +4764,7 @@ "aria-label-go-queryless": "" }, "raw-list-container": { + "item-count": "", "label-expand-results": "" }, "raw-list-item": { @@ -4649,7 +4780,6 @@ "newest-first": "Mais recentes primeiro", "oldest-first": "Mais antigos primeiro", "query-history": "Histórico de consultas", - "query-library": "Biblioteca de consultas", "settings": "Configurações", "starred": "Favorito" }, @@ -4765,6 +4895,9 @@ }, "show-critical-path-only": "", "show-matches-only": "", + "span-bar": { + "tooltip-critical-path": "" + }, "span-bar-section": { "description-link": "", "title-span-bar": "" @@ -4791,6 +4924,7 @@ "aria-label-select-service-name-operator": "", "aria-label-select-span-name": "", "aria-label-select-span-name-operator": "", + "label-collapse": "", "label-duration": "", "label-service-name": "", "label-span-name": "", @@ -4834,6 +4968,9 @@ "tooltip-expand": "", "tooltip-expand-all": "" }, + "timeline-header-row": { + "title": "" + }, "toolbar": { "add-to-extensions": "", "add-to-queryless-extensions": "Continuar sem consulta", @@ -4863,7 +5000,8 @@ "title-share-thoughts-about-tracing-grafana": "" }, "trace-page-header": { - "text-partial-trace": "" + "text-partial-trace": "", + "tooltip-url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "", @@ -4877,6 +5015,7 @@ "title-trace": "" }, "unconnected-node-graph-container": { + "count-warning": "", "title-node-graph": "" }, "unthemed-logs": { @@ -4930,11 +5069,20 @@ "classic-conditions": { "label-conditions": "" }, + "condition": { + "of": "", + "to": "", + "when": "" + }, "expression-query-editor": { "label-operation": "" }, "math": { - "available-math-functions": "" + "available-math-functions": "", + "run-math-operations": "", + "tooltip-footer": "", + "tooltip-title": "", + "tooltip-trigger": "" }, "reduce": { "label-function": "", @@ -5013,6 +5161,12 @@ }, "essentials": { "title-essentials": "" + }, + "progress-bar": { + "steps-status": "" + }, + "progress-status": { + "your-progress": "" } }, "grafana": { @@ -5404,8 +5558,14 @@ }, "inspector": { "inspect-data-tab": { + "loading": "", "no-data": "" }, + "inspect-error-tab": { + "error-status-message": "", + "error-status-no-message": "", + "error-trace-message": "" + }, "inspect-jsontab": { "apply": "", "support": "" @@ -5420,7 +5580,11 @@ }, "query-inspector": { "query-inspector": "", - "text-loading-query-inspector": "" + "text-loading-query-inspector": "", + "count-frames_one": "", + "count-frames_other": "", + "count-rows_one": "", + "count-rows_other": "" } }, "invites": { @@ -5432,6 +5596,11 @@ "name": "" }, "signup-invited-page": { + "complete-following": "", + "custom-has-invited-you": "", + "default-has-invited-you": "", + "greeting-custom": "", + "greeting-default": "", "label-email": "", "label-name": "", "label-password": "", @@ -5627,6 +5796,11 @@ "has-connected-dashboards": { "dashboard-name": "" }, + "library-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_other": "" + }, "library-panels-search": { "placeholder-search-by-name-or-description": "" }, @@ -5653,12 +5827,15 @@ "success": "Painel de biblioteca salvo" }, "save-library-panel-modal": { + "affected-dashboards": "", "cancel": "", "dashboard-name": "", "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "num-affected_one": "", + "num-affected_other": "" } }, "link": { @@ -5683,6 +5860,7 @@ "live": { "dashboard-changed-modal": { "continue-editing": "", + "description": "", "discard-local-changes": "", "title-dashboard-changed": "" }, @@ -5761,6 +5939,9 @@ "collapse": "", "expand": "" }, + "log-labels-list": { + "log-line": "" + }, "log-line-menu": { "copy-link": "Copiar link para a linha do log", "copy-log": "Copiar linha do log", @@ -5850,6 +6031,8 @@ "aria-label-no-details": "" }, "un-themed-log-details-row": { + "filter-out": "", + "filter-out-query": "", "title-copy-value-to-clipboard": "", "toggle-field-button": { "tooltip-field-instead-message": "", @@ -5857,6 +6040,10 @@ }, "tooltip-adhoc-statistics": "", "tooltip-hide-adhoc-statistics": "" + }, + "un-themed-log-label-stats": { + "field-log-stats": "", + "label-log-stats": "" } }, "manage-dashbaords": { @@ -5878,6 +6065,7 @@ "options": "" }, "import-dashboard-overview-un-connected": { + "importing-from": "", "published-by": "", "updated-on": "" }, @@ -5937,6 +6125,9 @@ "button": "Migrar esta instância para a nuvem", "header": "Deixe-nos gerenciar a sua pilha do Grafana" }, + "dashboard-info": { + "dashboard": "" + }, "delete-migration-token-confirm": { "body": "", "confirm-button": "Excluir token", @@ -5952,6 +6143,7 @@ "title": "Desconectar da pilha na nuvem" }, "folder-info": { + "folder": "", "unable-to-load-folder": "" }, "get-started": { @@ -6574,6 +6766,7 @@ "org": { "new-org-page": { "create": "", + "description": "", "label-organization-name": "", "placeholder-org-name": "" }, @@ -6582,6 +6775,9 @@ "label-organization-profile": "", "update-organization-name": "" }, + "select-org-page": { + "description": "" + }, "user-invite-form": { "back": "", "label-email-or-username": "", @@ -6589,7 +6785,11 @@ "label-send-invite-email": "", "placeholder-optional": "", "role": "", - "submit": "" + "submit": "", + "tooltip": "" + }, + "user-invite-page": { + "sub-title": "" } }, "org-picker": { @@ -6632,7 +6832,9 @@ "title-not-found": "" }, "panel-renderer": { + "failed-to-load-plugin": "", "loading-plugin-panel": "", + "no-panel-component": "", "no-panel-data": "" }, "panel-type-card": { @@ -6659,7 +6861,14 @@ "select-placeholder": "" }, "playlist": { + "playlist-table-rows": { + "aria-label-playlist-item": "", + "multiple-dashboards-found": "", + "no-dashboards-found": "", + "not-found": "" + }, "start-modal": { + "button-start": "", "description-customize-dashboard-elements-visibility": "", "description-panel-heights-adjusted-screen": "", "label-autofit": "", @@ -6719,6 +6928,11 @@ "message": "Nenhuma lista de reprodução encontrada" } }, + "plugin": { + "plugin-details-deprecated-warning": { + "body-deprecated": "" + } + }, "plugins": { "app-root-page": { "access-denied": { @@ -6822,6 +7036,12 @@ "add-new-data-source": "", "title-button-disabled": "" }, + "install-controls": { + "install": "", + "installing": "", + "update": "", + "updating": "" + }, "install-controls-button": { "title-uninstall-modal": "" }, @@ -6840,6 +7060,7 @@ "label-severity": "" }, "not-found-plugin": { + "body-plugin-not-found": "", "title-plugin-not-found": "" }, "plugin-actions": { @@ -6854,12 +7075,17 @@ "tooltip-plugin-deprecated-longer-receives-updates": "" }, "plugin-details-body": { + "needs-service-account": "", "page-not-found": "" }, "plugin-details-deprecated-warning": { "title-deprecated": "" }, + "plugin-details-header-dependencies": { + "grafana-dependency": "" + }, "plugin-details-signature": { + "body-invalid-plugin-signature": "", "read-more-about-plugins-signing": "", "title-invalid-plugin-signature": "" }, @@ -6890,6 +7116,8 @@ "update-available": "" }, "plugin-usage": { + "body-missing-feature-toggle-panel-title-search": "", + "num-usages": "", "title-missing-feature-toggle-panel-title-search": "", "title-not-used-yet": "" }, @@ -6906,7 +7134,9 @@ }, "version-list": { "grafana-dependency": "", + "installed-version": "", "last-updated": "", + "latest-compatible-version": "", "no-version-history-was-found": "", "version": "" } @@ -6929,6 +7159,12 @@ }, "change-theme": "", "enable-kiosk-mode": "", + "feature-toggle-page": { + "profile-not-enabled": "" + }, + "feature-toggles-age": { + "enable-in-config": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -7073,6 +7309,8 @@ "repository-config-exists-configuration": "" }, "file-status-page": { + "save": "", + "saving": "", "title-error-loading-file": "" }, "files-view": { @@ -7103,7 +7341,6 @@ }, "getting-started": { "alert-temporary-outage": "", - "engaging-graphic": "", "modal-description-public-access": "", "modal-description-required-features": "", "modal-title-set-up-public-access": "", @@ -7199,13 +7436,16 @@ "checked": "", "finished": "", "health": "", + "healthy": "", "job-id": "", "last-ref": "", "messages": "", + "not-available": "", "pull-status": "", "resources": "", "started": "", "status": "", + "unhealthy": "", "view-folder": "", "webhook": "", "webhook-events": "", @@ -7496,31 +7736,41 @@ "hidden": "", "query-name-div-title-edit-query-name": "" }, + "query-error-alert": { + "trace-id": "" + }, "query-group": { "add-query": "", "expression": "", "title-data-source-help": "" }, "query-group-options-editor": { + "collapsed-interval": "", + "collapsed-max-data-points": "", "hide-time-info": "", "Query options-title-query-options": "", "relative-time": "", + "relative-time-tooltip": "", "render-cache-timeout-option": { "cache-timeout": "" }, "render-interval-option": { "interval": "", + "interval-tooltip": "", "min-interval": "", + "min-interval-tooltip": "", "time-range-max-data-points": "" }, "render-max-data-points-option": { "max-data-points": "", + "max-data-points-tooltip": "", "width-of-panel": "" }, "render-query-caching-ttloption": { "cache-ttl": "" }, - "time-shift": "" + "time-shift": "", + "time-shift-tooltip": "" }, "query-group-top-section": { "data-source": "", @@ -7702,6 +7952,9 @@ } }, "search": { + "action-row": { + "panel-type": "" + }, "actions": { "include-panels": "Incluir painéis", "remove-datasource-filter": "Banco de dados: {{datasource}}", @@ -7825,6 +8078,9 @@ "label-roles": "", "used-by": "" }, + "service-account-profile-row": { + "edit": "" + }, "service-account-role-row": { "aria-label-role": "" }, @@ -8349,6 +8605,7 @@ "generate-enum-values-from-data": "" }, "enum-mapping-row": { + "click-to-edit": "", "remove-enum-row-aria-label-delete-enum-row": "", "remove-enum-row-tooltip-delete": "" }, @@ -8403,10 +8660,13 @@ "label-format": "", "label-set-timezone": "", "label-time-field": "", + "tooltip-format": "", "tooltip-timezone-manually": "" }, "get-tooltips": { - "json-value": "" + "description": "", + "json-value": "", + "valid-paths": "" }, "group-by-field-configuration": { "placeholder-ignored": "", @@ -8623,6 +8883,7 @@ "message": "Nenhum usuário encontrado" }, "token-revoked-modal": { + "auto-revoked": "", "resume-message": "", "sign-in": "", "title-you-have-been-automatically-signed-out": "" @@ -8717,7 +8978,8 @@ "general": "", "placeholder-descriptive-text": "", "placeholder-label-name": "", - "placeholder-variable-name": "" + "placeholder-variable-name": "", + "run-query": "" }, "variable-editor-list": { "definition": "", diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index 4d9f6db310a..3b5e34996d8 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -1,6 +1,5 @@ { "_comment": "O código é a fonte da verdade para as frases em inglês. Devem atualizar-se diretamente nos componentes e os plurais adicionais devem ser especificados neste ficheiro.", - "{{pageStart}} - {{pageEnd}} of {{numPages}}": "", "access-control": { "add-permission": { "built-in-aria-label": "", @@ -969,7 +968,8 @@ "expression-result": { "aria-label-nextpage": "", "aria-label-previouspage": "", - "no-data": "" + "no-data": "", + "page-counter": "" }, "federated-rule-warning": { "experimental": "", @@ -2004,6 +2004,10 @@ "title": "Regras de alerta" }, "rulerrule-loading-error": "Falha ao carregar a regra", + "toggle": { + "go-back-to-old-look": "", + "try-out-the-new-look": "" + }, "unknown-rule-type": "" }, "rule-list-errors": { @@ -2513,6 +2517,18 @@ "configuration-required": "", "refer-documentation-configure-authentication": "" }, + "fields": { + "allowed-groups-description": "", + "allowed-groups-description-oauth": "", + "api-url-description": "", + "team-ids-description": "", + "team-ids-description-oauth": "", + "team-ids-github": "", + "team-ids-other": "", + "teams-url-description": "", + "teams-url-description-oauth": "", + "use-pkce-description": "" + }, "provider-card": { "text-badge-enabled": "", "text-badge-not-enabled": "" @@ -2526,6 +2542,8 @@ "label-enabled": "", "reset-configuration": "", "reset-configuration-description": "", + "save": "", + "saving": "", "title-more-actions": "", "title-reset": "", "tooltip-more-actions": "" @@ -2671,6 +2689,9 @@ "canvas": { "not-found-display": { "not-found": "" + }, + "text-display": { + "double-click-to-set": "" } }, "carousel": { @@ -2947,7 +2968,9 @@ }, "annotation-settings-list": { "aria-label-delete": "", + "built-in": "", "data-source": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3060,6 +3083,7 @@ } }, "confirm-plugin-dashboard-save-modal": { + "body-plugin-dashboard": "", "cancel": "", "overwrite": "", "title-plugin-dashboard": "" @@ -3082,6 +3106,7 @@ } }, "dashboard-validation": { + "body-dashboard-failed-schema-validation": "", "title-checking-dashboard-validity": "", "title-dashboard-failed-schema-validation": "", "title-error-checking-dashboard-validity": "" @@ -3216,6 +3241,7 @@ }, "gen-aihistory": { "aria-label-send-custom-feedback": "", + "footer-text": "", "placeholder-tell-ai-what-to-do-next": "" }, "get-field-override-categories": { @@ -3306,9 +3332,13 @@ "make-editable": "" }, "minimalistic-pagination": { + "page-count": "", "tooltip-next": "", "tooltip-previous": "" }, + "new-dashboard-with-ds": { + "not-found": "" + }, "new-panel": { "configure-button": "", "configure-button-menu": "", @@ -3371,9 +3401,12 @@ "go-back-to-queries": "" }, "public-dashboard-not-available": { + "does-not-exist": "", + "paused": "", "try-again-later": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, "row-options-button": { @@ -3435,6 +3468,8 @@ "description": "", "label-copy-tags": "", "label-folder": "", + "save": "", + "saving": "", "title": "" }, "save-dashboard-button": { @@ -3451,19 +3486,30 @@ "label-details": "" }, "save-dashboard-error-proxy": { - "title-conflict": "" + "body-name-exists": "", + "body-version-mismatch": "", + "title-name-exists": "", + "title-version-mismatch": "" }, "save-dashboard-form": { "cancel": "", "label-current-range-dashboard-default": "", "label-current-variable-values-dashboard-default": "", "no-changes-to-save": "", - "placeholder-describe-changes": "" + "placeholder-describe-changes": "", + "save": "", + "saving": "" }, "save-provisioned-dashboard-form": { "cancel": "", + "cannot-be-saved": "", "copy-json-to-clipboard": "", - "save-json-to-file": "" + "file-path": "", + "save-json-to-file": "", + "see-docs": "" + }, + "share-public-dashboard-loader": { + "loading-configuration": "" }, "solo-panel": { "loading-initializing-dashboard": "", @@ -3642,6 +3688,9 @@ "title-delete": "" }, "transformation-picker": { + "info": "", + "info-graph-not-suitable": "", + "info-switch-to-table": "", "placeholder-search-for-transformation": "", "read-more": "", "title-transformations": "" @@ -3666,6 +3715,9 @@ "discard": "", "title-unsaved-changes": "" }, + "untheme-dashboard-row": { + "dashboard-datasource": "" + }, "unthemed-dashboard-row": { "aria-label-delete-row": "", "learn-more": "" @@ -3677,7 +3729,10 @@ "tags-expected-strings": "matriz de strings de etiquetas esperada" }, "version-history-comparison": { - "label-view-json-diff": "" + "button-restore": "", + "label-view-json-diff": "", + "new-updated-by": "", + "old-updated-by": "" }, "version-history-table": { "aria-label-toggle-selection": "", @@ -3776,8 +3831,10 @@ "title-annotation-support-source": "" }, "annotation-settings-list": { + "built-in": "", "data-source": "", "delete-aria-label": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3838,6 +3895,8 @@ }, "data-source-variable-form": { "data-source-options": "", + "description-instance-name-filter": "", + "example-instance-name-filter": "", "selection-options": "" }, "description-label": { @@ -3898,7 +3957,13 @@ "title-plugin-dashboard": "", "title-someone-else-has-updated-this-dashboard": "", "would-still-dashboard": "" - } + }, + "save-and-overwrite": "" + }, + "library-viz-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_other": "" }, "name-already-exists-error": { "body-name-already-exists": "", @@ -3908,6 +3973,7 @@ "alert": { "title-errors-loading-rules": "" }, + "error-failed-to-load": "", "text-loading-rules": "", "title-dashboard-not-saved": "" }, @@ -3937,7 +4003,10 @@ "title-close": "" }, "provisioned-delete-modal": { + "cannot-be-deleted": "", + "file-path": "", "ok": "", + "see-docs": "", "title-cannot-delete-provisioned-dashboard": "" }, "provisioned-resource-delete-modal": { @@ -3950,13 +4019,21 @@ "query": "" }, "query-variable-editor-form": { + "description-examples": "", + "description-optional": "", "label-data-source": "", "query-options": "", "selection-options": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, + "save-button": { + "save": "", + "save-and-overwrite": "", + "saving": "" + }, "save-dashboard-as-form": { "aria-label-save-dashboard-description-field": "", "aria-label-save-dashboard-title-field": "", @@ -4009,17 +4086,21 @@ "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "affected-dashboards_one": "", + "affected-dashboards_other": "" }, "save-provisioned-dashboard-form": { "api-error": "Erro ao guardar o painel de controlo", "api-success": "Alterações do painel de controlo guardadas", "cancel": "Cancelar", + "cannot-be-saved": "", "copy-json-message": "Se tiver acesso direto ao destino, copie o JSON e cole-o lá.", "copy-json-to-clipboard": "", "dashboard-comment-placeholder-describe-changes-optional": "Adicione uma nota para descrever as suas alterações (opcional)", "description-branch-name-in-git-hub": "Nome do ramo no GitHub", "description-inside-repository": "Caminho do ficheiro dentro do repositório (.json ou .yaml)", + "file-path": "", "label-branch": "Ramo", "label-comment": "Comentário", "label-description": "Descrição", @@ -4030,6 +4111,7 @@ "save": "Guardar", "save-json-to-file": "", "saving": "A guardar...", + "see-docs": "", "title-required": "O título do painel de controlo é obrigatório", "title-same-as-folder": "O nome do painel de controlo não pode ser igual ao nome da pasta", "title-this-repository-is-read-only": "Este repositório é apenas de leitura", @@ -4048,6 +4130,9 @@ "share-button": { "aria-label-sharedropdownmenu": "" }, + "solo-panel-page": { + "loading": "" + }, "text-box-variable-form": { "placeholder-default-value-if-any": "", "text-options": "" @@ -4107,10 +4192,18 @@ "preview-of-values": "", "show-more": "" }, + "version-history": { + "comparison": { + "button-restore": "" + } + }, "version-history-comparison": { - "label-view-json-diff": "" + "label-view-json-diff": "", + "new-version-updated": "", + "old-version-updated": "" }, "version-history-header": { + "compare-versions": "", "latest": "", "tooltip-reset-version": "" }, @@ -4286,6 +4379,7 @@ }, "button-row": { "delete": "", + "save-and-test": "", "test": "" }, "cloud-info-box": { @@ -4309,6 +4403,9 @@ "data-source-picker": { "aria-label-select-a-data-source": "" }, + "data-source-plugin-config-page": { + "page-not-found": "" + }, "data-source-plugin-state": { "plugin-state": "" }, @@ -4445,8 +4542,12 @@ } }, "explore": { + "accordian-logs": { + "footer": "" + }, "accordian-references": { - "references": "" + "references": "", + "view-linked-span": "" }, "add-to-dashboard": "Adicionar ao painel de controlo", "basic-extensions": { @@ -4455,6 +4556,7 @@ "clear": "", "confirm-navigation-modal": { "cancel": "", + "new-tab": "", "open": "", "open-in-new-tab": "" }, @@ -4475,9 +4577,14 @@ "add-transformation": "", "aria-label-delete-transformation": "", "aria-label-edit-transformation": "", + "body-correlation-details": "", + "expression": "", "label-description": "", + "label-description-header": "", "label-label": "", - "title-correlation-details": "" + "title-correlation-details": "", + "tooltip-transformations": "", + "transformations": "" }, "correlation-transformation-add-modal": { "add-transformation": "", @@ -4503,10 +4610,13 @@ "title-unsaved-changes-to-correlation": "" }, "draggable-manager-demo": { + "click-and-drag-gray-divider": "", "click-horizontally-somewhere-colored-below": "", + "drag-value": "", "draggable-manager-demo": "", "dragging-a-divider": "", - "dragging-a-sub-region": "" + "dragging-a-sub-region": "", + "value": "" }, "drilldownInfo": { "action": "Ir para Grafana Drilldown", @@ -4537,6 +4647,10 @@ "outline": "", "tooltip-content-outline": "" }, + "feature-toggle-page": { + "description-explore-disabled": "", + "title-explore-disabled": "" + }, "get-matches-metadata": { "matches": "" }, @@ -4547,10 +4661,15 @@ }, "live-logs": { "clear-logs": "", - "exit-live-mode": "" + "exit-live-mode": "", + "last-line-received": "", + "pause": "", + "resume": "" }, "live-tail-button": { + "live": "", "pause-the-live-stream": "", + "paused": "", "start-live-stream-your-logs": "", "stop-and-exit-the-live-stream": "" }, @@ -4579,12 +4698,14 @@ "show-original-line": "" }, "logs-sample-panel": { + "label": "", "logs-sample-is-loading": "", "no-logs-sample-data": "", "open-in-split-view-button": { "open-logs-in-split-view": "" }, - "title-failed-sample-query": "" + "title-failed-sample-query": "", + "tooltip": "" }, "logs-table-available-fields": { "title-label-percentage": "" @@ -4593,7 +4714,9 @@ "no-fields": "" }, "logs-table-multi-select": { - "fields": "" + "fields": "", + "reset": "", + "selected-fields": "" }, "logs-table-nav-field": { "aria-label-drag-and-drop-icon": "", @@ -4612,13 +4735,20 @@ "title-showing-partial-data": "", "title-unable-to-show-log-volume": "" }, + "logs-volumne-panel-list": { + "body-no-logs-volume-available": "" + }, "make-datasource-setup": { "aria-label-query": "" }, "next": "", "next-prev-result": { "aria-label-next": "", - "aria-label-prev": "" + "aria-label-prev": "", + "depth": "", + "depth-span-filter-matches": "", + "services": "", + "services-span-filter-matches": "" }, "no-data-source-call-to-action": { "cta-element": { @@ -4634,6 +4764,7 @@ "aria-label-go-queryless": "" }, "raw-list-container": { + "item-count": "", "label-expand-results": "" }, "raw-list-item": { @@ -4649,7 +4780,6 @@ "newest-first": "Mais recentes primeiro", "oldest-first": "Mais antigos primeiro", "query-history": "Histórico de consultas", - "query-library": "Biblioteca de consultas", "settings": "Definições", "starred": "Favoritos" }, @@ -4765,6 +4895,9 @@ }, "show-critical-path-only": "", "show-matches-only": "", + "span-bar": { + "tooltip-critical-path": "" + }, "span-bar-section": { "description-link": "", "title-span-bar": "" @@ -4791,6 +4924,7 @@ "aria-label-select-service-name-operator": "", "aria-label-select-span-name": "", "aria-label-select-span-name-operator": "", + "label-collapse": "", "label-duration": "", "label-service-name": "", "label-span-name": "", @@ -4834,6 +4968,9 @@ "tooltip-expand": "", "tooltip-expand-all": "" }, + "timeline-header-row": { + "title": "" + }, "toolbar": { "add-to-extensions": "Adicionar", "add-to-queryless-extensions": "Sem consulta", @@ -4863,7 +5000,8 @@ "title-share-thoughts-about-tracing-grafana": "" }, "trace-page-header": { - "text-partial-trace": "" + "text-partial-trace": "", + "tooltip-url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "", @@ -4877,6 +5015,7 @@ "title-trace": "" }, "unconnected-node-graph-container": { + "count-warning": "", "title-node-graph": "" }, "unthemed-logs": { @@ -4930,11 +5069,20 @@ "classic-conditions": { "label-conditions": "" }, + "condition": { + "of": "", + "to": "", + "when": "" + }, "expression-query-editor": { "label-operation": "" }, "math": { - "available-math-functions": "" + "available-math-functions": "", + "run-math-operations": "", + "tooltip-footer": "", + "tooltip-title": "", + "tooltip-trigger": "" }, "reduce": { "label-function": "", @@ -5013,6 +5161,12 @@ }, "essentials": { "title-essentials": "" + }, + "progress-bar": { + "steps-status": "" + }, + "progress-status": { + "your-progress": "" } }, "grafana": { @@ -5404,8 +5558,14 @@ }, "inspector": { "inspect-data-tab": { + "loading": "", "no-data": "" }, + "inspect-error-tab": { + "error-status-message": "", + "error-status-no-message": "", + "error-trace-message": "" + }, "inspect-jsontab": { "apply": "", "support": "" @@ -5420,7 +5580,11 @@ }, "query-inspector": { "query-inspector": "", - "text-loading-query-inspector": "" + "text-loading-query-inspector": "", + "count-frames_one": "", + "count-frames_other": "", + "count-rows_one": "", + "count-rows_other": "" } }, "invites": { @@ -5432,6 +5596,11 @@ "name": "" }, "signup-invited-page": { + "complete-following": "", + "custom-has-invited-you": "", + "default-has-invited-you": "", + "greeting-custom": "", + "greeting-default": "", "label-email": "", "label-name": "", "label-password": "", @@ -5627,6 +5796,11 @@ "has-connected-dashboards": { "dashboard-name": "" }, + "library-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_other": "" + }, "library-panels-search": { "placeholder-search-by-name-or-description": "" }, @@ -5653,12 +5827,15 @@ "success": "Painel de biblioteca guardado" }, "save-library-panel-modal": { + "affected-dashboards": "", "cancel": "", "dashboard-name": "", "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "num-affected_one": "", + "num-affected_other": "" } }, "link": { @@ -5683,6 +5860,7 @@ "live": { "dashboard-changed-modal": { "continue-editing": "", + "description": "", "discard-local-changes": "", "title-dashboard-changed": "" }, @@ -5761,6 +5939,9 @@ "collapse": "", "expand": "" }, + "log-labels-list": { + "log-line": "" + }, "log-line-menu": { "copy-link": "Copiar link para a linha de registo", "copy-log": "Copiar linha de registo", @@ -5850,6 +6031,8 @@ "aria-label-no-details": "" }, "un-themed-log-details-row": { + "filter-out": "", + "filter-out-query": "", "title-copy-value-to-clipboard": "", "toggle-field-button": { "tooltip-field-instead-message": "", @@ -5857,6 +6040,10 @@ }, "tooltip-adhoc-statistics": "", "tooltip-hide-adhoc-statistics": "" + }, + "un-themed-log-label-stats": { + "field-log-stats": "", + "label-log-stats": "" } }, "manage-dashbaords": { @@ -5878,6 +6065,7 @@ "options": "" }, "import-dashboard-overview-un-connected": { + "importing-from": "", "published-by": "", "updated-on": "" }, @@ -5937,6 +6125,9 @@ "button": "Migrar esta instância para a Cloud", "header": "Deixe-nos gerir a sua pilha da Grafana" }, + "dashboard-info": { + "dashboard": "" + }, "delete-migration-token-confirm": { "body": "Se já utilizou este token com uma instalação gerida automaticamente, essa instalação já não poderá carregar conteúdos.", "confirm-button": "Eliminar token", @@ -5952,6 +6143,7 @@ "title": "Desligar da pilha de nuvem" }, "folder-info": { + "folder": "", "unable-to-load-folder": "" }, "get-started": { @@ -6574,6 +6766,7 @@ "org": { "new-org-page": { "create": "", + "description": "", "label-organization-name": "", "placeholder-org-name": "" }, @@ -6582,6 +6775,9 @@ "label-organization-profile": "", "update-organization-name": "" }, + "select-org-page": { + "description": "" + }, "user-invite-form": { "back": "", "label-email-or-username": "", @@ -6589,7 +6785,11 @@ "label-send-invite-email": "", "placeholder-optional": "", "role": "", - "submit": "" + "submit": "", + "tooltip": "" + }, + "user-invite-page": { + "sub-title": "" } }, "org-picker": { @@ -6632,7 +6832,9 @@ "title-not-found": "" }, "panel-renderer": { + "failed-to-load-plugin": "", "loading-plugin-panel": "", + "no-panel-component": "", "no-panel-data": "" }, "panel-type-card": { @@ -6659,7 +6861,14 @@ "select-placeholder": "" }, "playlist": { + "playlist-table-rows": { + "aria-label-playlist-item": "", + "multiple-dashboards-found": "", + "no-dashboards-found": "", + "not-found": "" + }, "start-modal": { + "button-start": "", "description-customize-dashboard-elements-visibility": "", "description-panel-heights-adjusted-screen": "", "label-autofit": "", @@ -6719,6 +6928,11 @@ "message": "Nenhuma lista de reprodução encontrada" } }, + "plugin": { + "plugin-details-deprecated-warning": { + "body-deprecated": "" + } + }, "plugins": { "app-root-page": { "access-denied": { @@ -6822,6 +7036,12 @@ "add-new-data-source": "", "title-button-disabled": "" }, + "install-controls": { + "install": "", + "installing": "", + "update": "", + "updating": "" + }, "install-controls-button": { "title-uninstall-modal": "" }, @@ -6840,6 +7060,7 @@ "label-severity": "" }, "not-found-plugin": { + "body-plugin-not-found": "", "title-plugin-not-found": "" }, "plugin-actions": { @@ -6854,12 +7075,17 @@ "tooltip-plugin-deprecated-longer-receives-updates": "" }, "plugin-details-body": { + "needs-service-account": "", "page-not-found": "" }, "plugin-details-deprecated-warning": { "title-deprecated": "" }, + "plugin-details-header-dependencies": { + "grafana-dependency": "" + }, "plugin-details-signature": { + "body-invalid-plugin-signature": "", "read-more-about-plugins-signing": "", "title-invalid-plugin-signature": "" }, @@ -6890,6 +7116,8 @@ "update-available": "" }, "plugin-usage": { + "body-missing-feature-toggle-panel-title-search": "", + "num-usages": "", "title-missing-feature-toggle-panel-title-search": "", "title-not-used-yet": "" }, @@ -6906,7 +7134,9 @@ }, "version-list": { "grafana-dependency": "", + "installed-version": "", "last-updated": "", + "latest-compatible-version": "", "no-version-history-was-found": "", "version": "" } @@ -6929,6 +7159,12 @@ }, "change-theme": "Alterar tema", "enable-kiosk-mode": "", + "feature-toggle-page": { + "profile-not-enabled": "" + }, + "feature-toggles-age": { + "enable-in-config": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -7073,6 +7309,8 @@ "repository-config-exists-configuration": "" }, "file-status-page": { + "save": "", + "saving": "", "title-error-loading-file": "" }, "files-view": { @@ -7103,7 +7341,6 @@ }, "getting-started": { "alert-temporary-outage": "", - "engaging-graphic": "", "modal-description-public-access": "", "modal-description-required-features": "", "modal-title-set-up-public-access": "", @@ -7199,13 +7436,16 @@ "checked": "", "finished": "", "health": "", + "healthy": "", "job-id": "", "last-ref": "", "messages": "", + "not-available": "", "pull-status": "", "resources": "", "started": "", "status": "", + "unhealthy": "", "view-folder": "", "webhook": "", "webhook-events": "", @@ -7496,31 +7736,41 @@ "hidden": "", "query-name-div-title-edit-query-name": "" }, + "query-error-alert": { + "trace-id": "" + }, "query-group": { "add-query": "", "expression": "", "title-data-source-help": "" }, "query-group-options-editor": { + "collapsed-interval": "", + "collapsed-max-data-points": "", "hide-time-info": "", "Query options-title-query-options": "", "relative-time": "", + "relative-time-tooltip": "", "render-cache-timeout-option": { "cache-timeout": "" }, "render-interval-option": { "interval": "", + "interval-tooltip": "", "min-interval": "", + "min-interval-tooltip": "", "time-range-max-data-points": "" }, "render-max-data-points-option": { "max-data-points": "", + "max-data-points-tooltip": "", "width-of-panel": "" }, "render-query-caching-ttloption": { "cache-ttl": "" }, - "time-shift": "" + "time-shift": "", + "time-shift-tooltip": "" }, "query-group-top-section": { "data-source": "", @@ -7702,6 +7952,9 @@ } }, "search": { + "action-row": { + "panel-type": "" + }, "actions": { "include-panels": "Incluir painéis", "remove-datasource-filter": "Origem dos dados: {{datasource}}", @@ -7825,6 +8078,9 @@ "label-roles": "", "used-by": "" }, + "service-account-profile-row": { + "edit": "" + }, "service-account-role-row": { "aria-label-role": "" }, @@ -8349,6 +8605,7 @@ "generate-enum-values-from-data": "" }, "enum-mapping-row": { + "click-to-edit": "", "remove-enum-row-aria-label-delete-enum-row": "", "remove-enum-row-tooltip-delete": "" }, @@ -8403,10 +8660,13 @@ "label-format": "", "label-set-timezone": "", "label-time-field": "", + "tooltip-format": "", "tooltip-timezone-manually": "" }, "get-tooltips": { - "json-value": "" + "description": "", + "json-value": "", + "valid-paths": "" }, "group-by-field-configuration": { "placeholder-ignored": "", @@ -8623,6 +8883,7 @@ "message": "Nenhum utilizador encontrado" }, "token-revoked-modal": { + "auto-revoked": "", "resume-message": "", "sign-in": "", "title-you-have-been-automatically-signed-out": "" @@ -8717,7 +8978,8 @@ "general": "", "placeholder-descriptive-text": "", "placeholder-label-name": "", - "placeholder-variable-name": "" + "placeholder-variable-name": "", + "run-query": "" }, "variable-editor-list": { "definition": "", diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index 0be2897569f..77799cb4ec9 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -1,6 +1,5 @@ { "_comment": "Код является источником истины для английских фраз. Их следует менять непосредственно в компонентах, а дополнительные формы множественного числа указывать в этом файле.", - "{{pageStart}} - {{pageEnd}} of {{numPages}}": "", "access-control": { "add-permission": { "built-in-aria-label": "", @@ -981,7 +980,8 @@ "expression-result": { "aria-label-nextpage": "", "aria-label-previouspage": "", - "no-data": "" + "no-data": "", + "page-counter": "" }, "federated-rule-warning": { "experimental": "", @@ -2024,6 +2024,10 @@ "title": "Правила оповещения" }, "rulerrule-loading-error": "Не удалось загрузить правило", + "toggle": { + "go-back-to-old-look": "", + "try-out-the-new-look": "" + }, "unknown-rule-type": "" }, "rule-list-errors": { @@ -2539,6 +2543,18 @@ "configuration-required": "", "refer-documentation-configure-authentication": "" }, + "fields": { + "allowed-groups-description": "", + "allowed-groups-description-oauth": "", + "api-url-description": "", + "team-ids-description": "", + "team-ids-description-oauth": "", + "team-ids-github": "", + "team-ids-other": "", + "teams-url-description": "", + "teams-url-description-oauth": "", + "use-pkce-description": "" + }, "provider-card": { "text-badge-enabled": "", "text-badge-not-enabled": "" @@ -2552,6 +2568,8 @@ "label-enabled": "", "reset-configuration": "", "reset-configuration-description": "", + "save": "", + "saving": "", "title-more-actions": "", "title-reset": "", "tooltip-more-actions": "" @@ -2707,6 +2725,9 @@ "canvas": { "not-found-display": { "not-found": "" + }, + "text-display": { + "double-click-to-set": "" } }, "carousel": { @@ -2983,7 +3004,9 @@ }, "annotation-settings-list": { "aria-label-delete": "", + "built-in": "", "data-source": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3096,6 +3119,7 @@ } }, "confirm-plugin-dashboard-save-modal": { + "body-plugin-dashboard": "", "cancel": "", "overwrite": "", "title-plugin-dashboard": "" @@ -3118,6 +3142,7 @@ } }, "dashboard-validation": { + "body-dashboard-failed-schema-validation": "", "title-checking-dashboard-validity": "", "title-dashboard-failed-schema-validation": "", "title-error-checking-dashboard-validity": "" @@ -3252,6 +3277,7 @@ }, "gen-aihistory": { "aria-label-send-custom-feedback": "", + "footer-text": "", "placeholder-tell-ai-what-to-do-next": "" }, "get-field-override-categories": { @@ -3342,9 +3368,13 @@ "make-editable": "" }, "minimalistic-pagination": { + "page-count": "", "tooltip-next": "", "tooltip-previous": "" }, + "new-dashboard-with-ds": { + "not-found": "" + }, "new-panel": { "configure-button": "", "configure-button-menu": "", @@ -3409,9 +3439,12 @@ "go-back-to-queries": "" }, "public-dashboard-not-available": { + "does-not-exist": "", + "paused": "", "try-again-later": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, "row-options-button": { @@ -3473,6 +3506,8 @@ "description": "", "label-copy-tags": "", "label-folder": "", + "save": "", + "saving": "", "title": "" }, "save-dashboard-button": { @@ -3489,19 +3524,30 @@ "label-details": "" }, "save-dashboard-error-proxy": { - "title-conflict": "" + "body-name-exists": "", + "body-version-mismatch": "", + "title-name-exists": "", + "title-version-mismatch": "" }, "save-dashboard-form": { "cancel": "", "label-current-range-dashboard-default": "", "label-current-variable-values-dashboard-default": "", "no-changes-to-save": "", - "placeholder-describe-changes": "" + "placeholder-describe-changes": "", + "save": "", + "saving": "" }, "save-provisioned-dashboard-form": { "cancel": "", + "cannot-be-saved": "", "copy-json-to-clipboard": "", - "save-json-to-file": "" + "file-path": "", + "save-json-to-file": "", + "see-docs": "" + }, + "share-public-dashboard-loader": { + "loading-configuration": "" }, "solo-panel": { "loading-initializing-dashboard": "", @@ -3680,6 +3726,9 @@ "title-delete": "" }, "transformation-picker": { + "info": "", + "info-graph-not-suitable": "", + "info-switch-to-table": "", "placeholder-search-for-transformation": "", "read-more": "", "title-transformations": "" @@ -3704,6 +3753,9 @@ "discard": "", "title-unsaved-changes": "" }, + "untheme-dashboard-row": { + "dashboard-datasource": "" + }, "unthemed-dashboard-row": { "aria-label-delete-row": "", "learn-more": "" @@ -3715,7 +3767,10 @@ "tags-expected-strings": "ожидаемый массив строк тегов" }, "version-history-comparison": { - "label-view-json-diff": "" + "button-restore": "", + "label-view-json-diff": "", + "new-updated-by": "", + "old-updated-by": "" }, "version-history-table": { "aria-label-toggle-selection": "", @@ -3814,8 +3869,10 @@ "title-annotation-support-source": "" }, "annotation-settings-list": { + "built-in": "", "data-source": "", "delete-aria-label": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3876,6 +3933,8 @@ }, "data-source-variable-form": { "data-source-options": "", + "description-instance-name-filter": "", + "example-instance-name-filter": "", "selection-options": "" }, "description-label": { @@ -3936,7 +3995,15 @@ "title-plugin-dashboard": "", "title-someone-else-has-updated-this-dashboard": "", "would-still-dashboard": "" - } + }, + "save-and-overwrite": "" + }, + "library-viz-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_few": "", + "usage-count_many": "", + "usage-count_other": "" }, "name-already-exists-error": { "body-name-already-exists": "", @@ -3946,6 +4013,7 @@ "alert": { "title-errors-loading-rules": "" }, + "error-failed-to-load": "", "text-loading-rules": "", "title-dashboard-not-saved": "" }, @@ -3975,7 +4043,10 @@ "title-close": "" }, "provisioned-delete-modal": { + "cannot-be-deleted": "", + "file-path": "", "ok": "", + "see-docs": "", "title-cannot-delete-provisioned-dashboard": "" }, "provisioned-resource-delete-modal": { @@ -3988,13 +4059,21 @@ "query": "" }, "query-variable-editor-form": { + "description-examples": "", + "description-optional": "", "label-data-source": "", "query-options": "", "selection-options": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, + "save-button": { + "save": "", + "save-and-overwrite": "", + "saving": "" + }, "save-dashboard-as-form": { "aria-label-save-dashboard-description-field": "", "aria-label-save-dashboard-title-field": "", @@ -4047,17 +4126,23 @@ "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "affected-dashboards_one": "", + "affected-dashboards_few": "", + "affected-dashboards_many": "", + "affected-dashboards_other": "" }, "save-provisioned-dashboard-form": { "api-error": "Ошибка при сохранении дашборда", "api-success": "Изменения в дашборде сохранены", "cancel": "Отмена", + "cannot-be-saved": "", "copy-json-message": "Если у вас есть прямой доступ к целевому объекту, скопируйте JSON-файл и вставьте его туда.", "copy-json-to-clipboard": "", "dashboard-comment-placeholder-describe-changes-optional": "Добавьте примечание с описанием изменений (необязательно)", "description-branch-name-in-git-hub": "Имя ветви в GitHub", "description-inside-repository": "Путь к файлу внутри репозитория (.json или .yaml)", + "file-path": "", "label-branch": "Ветвь", "label-comment": "Комментарий", "label-description": "Описание", @@ -4068,6 +4153,7 @@ "save": "Сохранить", "save-json-to-file": "", "saving": "Сохранение...", + "see-docs": "", "title-required": "Требуется указать название дашборда", "title-same-as-folder": "Имя дашборда не может совпадать с именем папки", "title-this-repository-is-read-only": "Репозиторий предназначен только для чтения", @@ -4086,6 +4172,9 @@ "share-button": { "aria-label-sharedropdownmenu": "" }, + "solo-panel-page": { + "loading": "" + }, "text-box-variable-form": { "placeholder-default-value-if-any": "", "text-options": "" @@ -4145,10 +4234,18 @@ "preview-of-values": "", "show-more": "" }, + "version-history": { + "comparison": { + "button-restore": "" + } + }, "version-history-comparison": { - "label-view-json-diff": "" + "label-view-json-diff": "", + "new-version-updated": "", + "old-version-updated": "" }, "version-history-header": { + "compare-versions": "", "latest": "", "tooltip-reset-version": "" }, @@ -4324,6 +4421,7 @@ }, "button-row": { "delete": "", + "save-and-test": "", "test": "" }, "cloud-info-box": { @@ -4347,6 +4445,9 @@ "data-source-picker": { "aria-label-select-a-data-source": "" }, + "data-source-plugin-config-page": { + "page-not-found": "" + }, "data-source-plugin-state": { "plugin-state": "" }, @@ -4483,8 +4584,12 @@ } }, "explore": { + "accordian-logs": { + "footer": "" + }, "accordian-references": { - "references": "" + "references": "", + "view-linked-span": "" }, "add-to-dashboard": "Добавить на дашборд", "basic-extensions": { @@ -4493,6 +4598,7 @@ "clear": "", "confirm-navigation-modal": { "cancel": "", + "new-tab": "", "open": "", "open-in-new-tab": "" }, @@ -4513,9 +4619,14 @@ "add-transformation": "", "aria-label-delete-transformation": "", "aria-label-edit-transformation": "", + "body-correlation-details": "", + "expression": "", "label-description": "", + "label-description-header": "", "label-label": "", - "title-correlation-details": "" + "title-correlation-details": "", + "tooltip-transformations": "", + "transformations": "" }, "correlation-transformation-add-modal": { "add-transformation": "", @@ -4541,10 +4652,13 @@ "title-unsaved-changes-to-correlation": "" }, "draggable-manager-demo": { + "click-and-drag-gray-divider": "", "click-horizontally-somewhere-colored-below": "", + "drag-value": "", "draggable-manager-demo": "", "dragging-a-divider": "", - "dragging-a-sub-region": "" + "dragging-a-sub-region": "", + "value": "" }, "drilldownInfo": { "action": "Перейти в Grafana Drilldown", @@ -4575,6 +4689,10 @@ "outline": "", "tooltip-content-outline": "" }, + "feature-toggle-page": { + "description-explore-disabled": "", + "title-explore-disabled": "" + }, "get-matches-metadata": { "matches": "" }, @@ -4585,10 +4703,15 @@ }, "live-logs": { "clear-logs": "", - "exit-live-mode": "" + "exit-live-mode": "", + "last-line-received": "", + "pause": "", + "resume": "" }, "live-tail-button": { + "live": "", "pause-the-live-stream": "", + "paused": "", "start-live-stream-your-logs": "", "stop-and-exit-the-live-stream": "" }, @@ -4617,12 +4740,14 @@ "show-original-line": "" }, "logs-sample-panel": { + "label": "", "logs-sample-is-loading": "", "no-logs-sample-data": "", "open-in-split-view-button": { "open-logs-in-split-view": "" }, - "title-failed-sample-query": "" + "title-failed-sample-query": "", + "tooltip": "" }, "logs-table-available-fields": { "title-label-percentage": "" @@ -4631,7 +4756,9 @@ "no-fields": "" }, "logs-table-multi-select": { - "fields": "" + "fields": "", + "reset": "", + "selected-fields": "" }, "logs-table-nav-field": { "aria-label-drag-and-drop-icon": "", @@ -4650,13 +4777,20 @@ "title-showing-partial-data": "", "title-unable-to-show-log-volume": "" }, + "logs-volumne-panel-list": { + "body-no-logs-volume-available": "" + }, "make-datasource-setup": { "aria-label-query": "" }, "next": "", "next-prev-result": { "aria-label-next": "", - "aria-label-prev": "" + "aria-label-prev": "", + "depth": "", + "depth-span-filter-matches": "", + "services": "", + "services-span-filter-matches": "" }, "no-data-source-call-to-action": { "cta-element": { @@ -4672,6 +4806,7 @@ "aria-label-go-queryless": "" }, "raw-list-container": { + "item-count": "", "label-expand-results": "" }, "raw-list-item": { @@ -4687,7 +4822,6 @@ "newest-first": "Сначала новые", "oldest-first": "Сначала старые", "query-history": "История запросов", - "query-library": "Библиотека запросов", "settings": "Параметры", "starred": "Помеченные" }, @@ -4803,6 +4937,9 @@ }, "show-critical-path-only": "", "show-matches-only": "", + "span-bar": { + "tooltip-critical-path": "" + }, "span-bar-section": { "description-link": "", "title-span-bar": "" @@ -4829,6 +4966,7 @@ "aria-label-select-service-name-operator": "", "aria-label-select-span-name": "", "aria-label-select-span-name-operator": "", + "label-collapse": "", "label-duration": "", "label-service-name": "", "label-span-name": "", @@ -4872,6 +5010,9 @@ "tooltip-expand": "", "tooltip-expand-all": "" }, + "timeline-header-row": { + "title": "" + }, "toolbar": { "add-to-extensions": "Добавить", "add-to-queryless-extensions": "Не выполнять запрос", @@ -4901,7 +5042,8 @@ "title-share-thoughts-about-tracing-grafana": "" }, "trace-page-header": { - "text-partial-trace": "" + "text-partial-trace": "", + "tooltip-url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "", @@ -4915,6 +5057,7 @@ "title-trace": "" }, "unconnected-node-graph-container": { + "count-warning": "", "title-node-graph": "" }, "unthemed-logs": { @@ -4968,11 +5111,20 @@ "classic-conditions": { "label-conditions": "" }, + "condition": { + "of": "", + "to": "", + "when": "" + }, "expression-query-editor": { "label-operation": "" }, "math": { - "available-math-functions": "" + "available-math-functions": "", + "run-math-operations": "", + "tooltip-footer": "", + "tooltip-title": "", + "tooltip-trigger": "" }, "reduce": { "label-function": "", @@ -5051,6 +5203,12 @@ }, "essentials": { "title-essentials": "" + }, + "progress-bar": { + "steps-status": "" + }, + "progress-status": { + "your-progress": "" } }, "grafana": { @@ -5442,8 +5600,14 @@ }, "inspector": { "inspect-data-tab": { + "loading": "", "no-data": "" }, + "inspect-error-tab": { + "error-status-message": "", + "error-status-no-message": "", + "error-trace-message": "" + }, "inspect-jsontab": { "apply": "", "support": "" @@ -5458,7 +5622,15 @@ }, "query-inspector": { "query-inspector": "", - "text-loading-query-inspector": "" + "text-loading-query-inspector": "", + "count-frames_one": "", + "count-frames_few": "", + "count-frames_many": "", + "count-frames_other": "", + "count-rows_one": "", + "count-rows_few": "", + "count-rows_many": "", + "count-rows_other": "" } }, "invites": { @@ -5470,6 +5642,11 @@ "name": "" }, "signup-invited-page": { + "complete-following": "", + "custom-has-invited-you": "", + "default-has-invited-you": "", + "greeting-custom": "", + "greeting-default": "", "label-email": "", "label-name": "", "label-password": "", @@ -5665,6 +5842,13 @@ "has-connected-dashboards": { "dashboard-name": "" }, + "library-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_few": "", + "usage-count_many": "", + "usage-count_other": "" + }, "library-panels-search": { "placeholder-search-by-name-or-description": "" }, @@ -5693,12 +5877,17 @@ "success": "Панель библиотеки сохранена" }, "save-library-panel-modal": { + "affected-dashboards": "", "cancel": "", "dashboard-name": "", "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "num-affected_one": "", + "num-affected_few": "", + "num-affected_many": "", + "num-affected_other": "" } }, "link": { @@ -5723,6 +5912,7 @@ "live": { "dashboard-changed-modal": { "continue-editing": "", + "description": "", "discard-local-changes": "", "title-dashboard-changed": "" }, @@ -5801,6 +5991,9 @@ "collapse": "", "expand": "" }, + "log-labels-list": { + "log-line": "" + }, "log-line-menu": { "copy-link": "Копировать ссылку в строку журнала", "copy-log": "Копировать строку журнала", @@ -5890,6 +6083,8 @@ "aria-label-no-details": "" }, "un-themed-log-details-row": { + "filter-out": "", + "filter-out-query": "", "title-copy-value-to-clipboard": "", "toggle-field-button": { "tooltip-field-instead-message": "", @@ -5897,6 +6092,10 @@ }, "tooltip-adhoc-statistics": "", "tooltip-hide-adhoc-statistics": "" + }, + "un-themed-log-label-stats": { + "field-log-stats": "", + "label-log-stats": "" } }, "manage-dashbaords": { @@ -5918,6 +6117,7 @@ "options": "" }, "import-dashboard-overview-un-connected": { + "importing-from": "", "published-by": "", "updated-on": "" }, @@ -5977,6 +6177,9 @@ "button": "Перенести экземпляр в Cloud", "header": "Управление вашим стеком Grafana" }, + "dashboard-info": { + "dashboard": "" + }, "delete-migration-token-confirm": { "body": "Если вы уже использовали этот токен для автономного экземпляра, то он больше не сможет загружать контент.", "confirm-button": "Удалить токен", @@ -5992,6 +6195,7 @@ "title": "Отключение от облачного стека" }, "folder-info": { + "folder": "", "unable-to-load-folder": "" }, "get-started": { @@ -6614,6 +6818,7 @@ "org": { "new-org-page": { "create": "", + "description": "", "label-organization-name": "", "placeholder-org-name": "" }, @@ -6622,6 +6827,9 @@ "label-organization-profile": "", "update-organization-name": "" }, + "select-org-page": { + "description": "" + }, "user-invite-form": { "back": "", "label-email-or-username": "", @@ -6629,7 +6837,11 @@ "label-send-invite-email": "", "placeholder-optional": "", "role": "", - "submit": "" + "submit": "", + "tooltip": "" + }, + "user-invite-page": { + "sub-title": "" } }, "org-picker": { @@ -6672,7 +6884,9 @@ "title-not-found": "" }, "panel-renderer": { + "failed-to-load-plugin": "", "loading-plugin-panel": "", + "no-panel-component": "", "no-panel-data": "" }, "panel-type-card": { @@ -6699,7 +6913,14 @@ "select-placeholder": "" }, "playlist": { + "playlist-table-rows": { + "aria-label-playlist-item": "", + "multiple-dashboards-found": "", + "no-dashboards-found": "", + "not-found": "" + }, "start-modal": { + "button-start": "", "description-customize-dashboard-elements-visibility": "", "description-panel-heights-adjusted-screen": "", "label-autofit": "", @@ -6759,6 +6980,11 @@ "message": "Плейлисты не найдены" } }, + "plugin": { + "plugin-details-deprecated-warning": { + "body-deprecated": "" + } + }, "plugins": { "app-root-page": { "access-denied": { @@ -6862,6 +7088,12 @@ "add-new-data-source": "", "title-button-disabled": "" }, + "install-controls": { + "install": "", + "installing": "", + "update": "", + "updating": "" + }, "install-controls-button": { "title-uninstall-modal": "" }, @@ -6880,6 +7112,7 @@ "label-severity": "" }, "not-found-plugin": { + "body-plugin-not-found": "", "title-plugin-not-found": "" }, "plugin-actions": { @@ -6894,12 +7127,17 @@ "tooltip-plugin-deprecated-longer-receives-updates": "" }, "plugin-details-body": { + "needs-service-account": "", "page-not-found": "" }, "plugin-details-deprecated-warning": { "title-deprecated": "" }, + "plugin-details-header-dependencies": { + "grafana-dependency": "" + }, "plugin-details-signature": { + "body-invalid-plugin-signature": "", "read-more-about-plugins-signing": "", "title-invalid-plugin-signature": "" }, @@ -6930,6 +7168,8 @@ "update-available": "" }, "plugin-usage": { + "body-missing-feature-toggle-panel-title-search": "", + "num-usages": "", "title-missing-feature-toggle-panel-title-search": "", "title-not-used-yet": "" }, @@ -6946,7 +7186,9 @@ }, "version-list": { "grafana-dependency": "", + "installed-version": "", "last-updated": "", + "latest-compatible-version": "", "no-version-history-was-found": "", "version": "" } @@ -6969,6 +7211,12 @@ }, "change-theme": "Изменить тему", "enable-kiosk-mode": "", + "feature-toggle-page": { + "profile-not-enabled": "" + }, + "feature-toggles-age": { + "enable-in-config": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -7115,6 +7363,8 @@ "repository-config-exists-configuration": "" }, "file-status-page": { + "save": "", + "saving": "", "title-error-loading-file": "" }, "files-view": { @@ -7145,7 +7395,6 @@ }, "getting-started": { "alert-temporary-outage": "", - "engaging-graphic": "", "modal-description-public-access": "", "modal-description-required-features": "", "modal-title-set-up-public-access": "", @@ -7241,13 +7490,16 @@ "checked": "", "finished": "", "health": "", + "healthy": "", "job-id": "", "last-ref": "", "messages": "", + "not-available": "", "pull-status": "", "resources": "", "started": "", "status": "", + "unhealthy": "", "view-folder": "", "webhook": "", "webhook-events": "", @@ -7540,31 +7792,41 @@ "hidden": "", "query-name-div-title-edit-query-name": "" }, + "query-error-alert": { + "trace-id": "" + }, "query-group": { "add-query": "", "expression": "", "title-data-source-help": "" }, "query-group-options-editor": { + "collapsed-interval": "", + "collapsed-max-data-points": "", "hide-time-info": "", "Query options-title-query-options": "", "relative-time": "", + "relative-time-tooltip": "", "render-cache-timeout-option": { "cache-timeout": "" }, "render-interval-option": { "interval": "", + "interval-tooltip": "", "min-interval": "", + "min-interval-tooltip": "", "time-range-max-data-points": "" }, "render-max-data-points-option": { "max-data-points": "", + "max-data-points-tooltip": "", "width-of-panel": "" }, "render-query-caching-ttloption": { "cache-ttl": "" }, - "time-shift": "" + "time-shift": "", + "time-shift-tooltip": "" }, "query-group-top-section": { "data-source": "", @@ -7752,6 +8014,9 @@ } }, "search": { + "action-row": { + "panel-type": "" + }, "actions": { "include-panels": "Включить панели", "remove-datasource-filter": "Источник данных: {{datasource}} ", @@ -7875,6 +8140,9 @@ "label-roles": "", "used-by": "" }, + "service-account-profile-row": { + "edit": "" + }, "service-account-role-row": { "aria-label-role": "" }, @@ -8399,6 +8667,7 @@ "generate-enum-values-from-data": "" }, "enum-mapping-row": { + "click-to-edit": "", "remove-enum-row-aria-label-delete-enum-row": "", "remove-enum-row-tooltip-delete": "" }, @@ -8453,10 +8722,13 @@ "label-format": "", "label-set-timezone": "", "label-time-field": "", + "tooltip-format": "", "tooltip-timezone-manually": "" }, "get-tooltips": { - "json-value": "" + "description": "", + "json-value": "", + "valid-paths": "" }, "group-by-field-configuration": { "placeholder-ignored": "", @@ -8673,6 +8945,7 @@ "message": "Пользователи не найдены" }, "token-revoked-modal": { + "auto-revoked": "", "resume-message": "", "sign-in": "", "title-you-have-been-automatically-signed-out": "" @@ -8767,7 +9040,8 @@ "general": "", "placeholder-descriptive-text": "", "placeholder-label-name": "", - "placeholder-variable-name": "" + "placeholder-variable-name": "", + "run-query": "" }, "variable-editor-list": { "definition": "", diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index 9867d1c4321..0dd371119bd 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -1,6 +1,5 @@ { "_comment": "Koden är sanningskällan för engelska fraser. Uppdateringar bör göras direkt i komponenterna och ytterligare pluralformer anges i denna fil.", - "{{pageStart}} - {{pageEnd}} of {{numPages}}": "", "access-control": { "add-permission": { "built-in-aria-label": "", @@ -969,7 +968,8 @@ "expression-result": { "aria-label-nextpage": "", "aria-label-previouspage": "", - "no-data": "" + "no-data": "", + "page-counter": "" }, "federated-rule-warning": { "experimental": "", @@ -2004,6 +2004,10 @@ "title": "Varningsregler" }, "rulerrule-loading-error": "Det gick inte att ladda regeln", + "toggle": { + "go-back-to-old-look": "", + "try-out-the-new-look": "" + }, "unknown-rule-type": "" }, "rule-list-errors": { @@ -2513,6 +2517,18 @@ "configuration-required": "", "refer-documentation-configure-authentication": "" }, + "fields": { + "allowed-groups-description": "", + "allowed-groups-description-oauth": "", + "api-url-description": "", + "team-ids-description": "", + "team-ids-description-oauth": "", + "team-ids-github": "", + "team-ids-other": "", + "teams-url-description": "", + "teams-url-description-oauth": "", + "use-pkce-description": "" + }, "provider-card": { "text-badge-enabled": "", "text-badge-not-enabled": "" @@ -2526,6 +2542,8 @@ "label-enabled": "", "reset-configuration": "", "reset-configuration-description": "", + "save": "", + "saving": "", "title-more-actions": "", "title-reset": "", "tooltip-more-actions": "" @@ -2671,6 +2689,9 @@ "canvas": { "not-found-display": { "not-found": "" + }, + "text-display": { + "double-click-to-set": "" } }, "carousel": { @@ -2947,7 +2968,9 @@ }, "annotation-settings-list": { "aria-label-delete": "", + "built-in": "", "data-source": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3060,6 +3083,7 @@ } }, "confirm-plugin-dashboard-save-modal": { + "body-plugin-dashboard": "", "cancel": "", "overwrite": "", "title-plugin-dashboard": "" @@ -3082,6 +3106,7 @@ } }, "dashboard-validation": { + "body-dashboard-failed-schema-validation": "", "title-checking-dashboard-validity": "", "title-dashboard-failed-schema-validation": "", "title-error-checking-dashboard-validity": "" @@ -3216,6 +3241,7 @@ }, "gen-aihistory": { "aria-label-send-custom-feedback": "", + "footer-text": "", "placeholder-tell-ai-what-to-do-next": "" }, "get-field-override-categories": { @@ -3306,9 +3332,13 @@ "make-editable": "" }, "minimalistic-pagination": { + "page-count": "", "tooltip-next": "", "tooltip-previous": "" }, + "new-dashboard-with-ds": { + "not-found": "" + }, "new-panel": { "configure-button": "", "configure-button-menu": "", @@ -3371,9 +3401,12 @@ "go-back-to-queries": "" }, "public-dashboard-not-available": { + "does-not-exist": "", + "paused": "", "try-again-later": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, "row-options-button": { @@ -3435,6 +3468,8 @@ "description": "", "label-copy-tags": "", "label-folder": "", + "save": "", + "saving": "", "title": "" }, "save-dashboard-button": { @@ -3451,19 +3486,30 @@ "label-details": "" }, "save-dashboard-error-proxy": { - "title-conflict": "" + "body-name-exists": "", + "body-version-mismatch": "", + "title-name-exists": "", + "title-version-mismatch": "" }, "save-dashboard-form": { "cancel": "", "label-current-range-dashboard-default": "", "label-current-variable-values-dashboard-default": "", "no-changes-to-save": "", - "placeholder-describe-changes": "" + "placeholder-describe-changes": "", + "save": "", + "saving": "" }, "save-provisioned-dashboard-form": { "cancel": "", + "cannot-be-saved": "", "copy-json-to-clipboard": "", - "save-json-to-file": "" + "file-path": "", + "save-json-to-file": "", + "see-docs": "" + }, + "share-public-dashboard-loader": { + "loading-configuration": "" }, "solo-panel": { "loading-initializing-dashboard": "", @@ -3642,6 +3688,9 @@ "title-delete": "" }, "transformation-picker": { + "info": "", + "info-graph-not-suitable": "", + "info-switch-to-table": "", "placeholder-search-for-transformation": "", "read-more": "", "title-transformations": "" @@ -3666,6 +3715,9 @@ "discard": "", "title-unsaved-changes": "" }, + "untheme-dashboard-row": { + "dashboard-datasource": "" + }, "unthemed-dashboard-row": { "aria-label-delete-row": "", "learn-more": "" @@ -3677,7 +3729,10 @@ "tags-expected-strings": "taggar förväntade strängmatris" }, "version-history-comparison": { - "label-view-json-diff": "" + "button-restore": "", + "label-view-json-diff": "", + "new-updated-by": "", + "old-updated-by": "" }, "version-history-table": { "aria-label-toggle-selection": "", @@ -3776,8 +3831,10 @@ "title-annotation-support-source": "" }, "annotation-settings-list": { + "built-in": "", "data-source": "", "delete-aria-label": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3838,6 +3895,8 @@ }, "data-source-variable-form": { "data-source-options": "", + "description-instance-name-filter": "", + "example-instance-name-filter": "", "selection-options": "" }, "description-label": { @@ -3898,7 +3957,13 @@ "title-plugin-dashboard": "", "title-someone-else-has-updated-this-dashboard": "", "would-still-dashboard": "" - } + }, + "save-and-overwrite": "" + }, + "library-viz-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_other": "" }, "name-already-exists-error": { "body-name-already-exists": "", @@ -3908,6 +3973,7 @@ "alert": { "title-errors-loading-rules": "" }, + "error-failed-to-load": "", "text-loading-rules": "", "title-dashboard-not-saved": "" }, @@ -3937,7 +4003,10 @@ "title-close": "" }, "provisioned-delete-modal": { + "cannot-be-deleted": "", + "file-path": "", "ok": "", + "see-docs": "", "title-cannot-delete-provisioned-dashboard": "" }, "provisioned-resource-delete-modal": { @@ -3950,13 +4019,21 @@ "query": "" }, "query-variable-editor-form": { + "description-examples": "", + "description-optional": "", "label-data-source": "", "query-options": "", "selection-options": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, + "save-button": { + "save": "", + "save-and-overwrite": "", + "saving": "" + }, "save-dashboard-as-form": { "aria-label-save-dashboard-description-field": "", "aria-label-save-dashboard-title-field": "", @@ -4009,17 +4086,21 @@ "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "affected-dashboards_one": "", + "affected-dashboards_other": "" }, "save-provisioned-dashboard-form": { "api-error": "Fel när instrumentpanel skulle sparas", "api-success": "Ändringarna i instrumentpanelen har sparats", "cancel": "Avbryt", + "cannot-be-saved": "", "copy-json-message": "Om du har direkt åtkomst till målet kopierar du JSON och klistrar in den där.", "copy-json-to-clipboard": "", "dashboard-comment-placeholder-describe-changes-optional": "Lägg till en anteckning för att beskriva dina ändringar (valfritt)", "description-branch-name-in-git-hub": "Grennamn i GitHub", "description-inside-repository": "Filsökväg inuti databasen (.json eller .yaml)", + "file-path": "", "label-branch": "Gren", "label-comment": "Kommentar", "label-description": "Beskrivning", @@ -4030,6 +4111,7 @@ "save": "Spara", "save-json-to-file": "", "saving": "Sparar ...", + "see-docs": "", "title-required": "Instrumentpanelens titel är obligatorisk", "title-same-as-folder": "Panelenamnet kan inte vara detsamma som mappnamnet", "title-this-repository-is-read-only": "Den här databasen är skrivskyddad", @@ -4048,6 +4130,9 @@ "share-button": { "aria-label-sharedropdownmenu": "" }, + "solo-panel-page": { + "loading": "" + }, "text-box-variable-form": { "placeholder-default-value-if-any": "", "text-options": "" @@ -4107,10 +4192,18 @@ "preview-of-values": "", "show-more": "" }, + "version-history": { + "comparison": { + "button-restore": "" + } + }, "version-history-comparison": { - "label-view-json-diff": "" + "label-view-json-diff": "", + "new-version-updated": "", + "old-version-updated": "" }, "version-history-header": { + "compare-versions": "", "latest": "", "tooltip-reset-version": "" }, @@ -4286,6 +4379,7 @@ }, "button-row": { "delete": "", + "save-and-test": "", "test": "" }, "cloud-info-box": { @@ -4309,6 +4403,9 @@ "data-source-picker": { "aria-label-select-a-data-source": "" }, + "data-source-plugin-config-page": { + "page-not-found": "" + }, "data-source-plugin-state": { "plugin-state": "" }, @@ -4445,8 +4542,12 @@ } }, "explore": { + "accordian-logs": { + "footer": "" + }, "accordian-references": { - "references": "" + "references": "", + "view-linked-span": "" }, "add-to-dashboard": "Lägg till i instrumentpanelen", "basic-extensions": { @@ -4455,6 +4556,7 @@ "clear": "", "confirm-navigation-modal": { "cancel": "", + "new-tab": "", "open": "", "open-in-new-tab": "" }, @@ -4475,9 +4577,14 @@ "add-transformation": "", "aria-label-delete-transformation": "", "aria-label-edit-transformation": "", + "body-correlation-details": "", + "expression": "", "label-description": "", + "label-description-header": "", "label-label": "", - "title-correlation-details": "" + "title-correlation-details": "", + "tooltip-transformations": "", + "transformations": "" }, "correlation-transformation-add-modal": { "add-transformation": "", @@ -4503,10 +4610,13 @@ "title-unsaved-changes-to-correlation": "" }, "draggable-manager-demo": { + "click-and-drag-gray-divider": "", "click-horizontally-somewhere-colored-below": "", + "drag-value": "", "draggable-manager-demo": "", "dragging-a-divider": "", - "dragging-a-sub-region": "" + "dragging-a-sub-region": "", + "value": "" }, "drilldownInfo": { "action": "Gå till Grafana Drilldown", @@ -4537,6 +4647,10 @@ "outline": "", "tooltip-content-outline": "" }, + "feature-toggle-page": { + "description-explore-disabled": "", + "title-explore-disabled": "" + }, "get-matches-metadata": { "matches": "" }, @@ -4547,10 +4661,15 @@ }, "live-logs": { "clear-logs": "", - "exit-live-mode": "" + "exit-live-mode": "", + "last-line-received": "", + "pause": "", + "resume": "" }, "live-tail-button": { + "live": "", "pause-the-live-stream": "", + "paused": "", "start-live-stream-your-logs": "", "stop-and-exit-the-live-stream": "" }, @@ -4579,12 +4698,14 @@ "show-original-line": "" }, "logs-sample-panel": { + "label": "", "logs-sample-is-loading": "", "no-logs-sample-data": "", "open-in-split-view-button": { "open-logs-in-split-view": "" }, - "title-failed-sample-query": "" + "title-failed-sample-query": "", + "tooltip": "" }, "logs-table-available-fields": { "title-label-percentage": "" @@ -4593,7 +4714,9 @@ "no-fields": "" }, "logs-table-multi-select": { - "fields": "" + "fields": "", + "reset": "", + "selected-fields": "" }, "logs-table-nav-field": { "aria-label-drag-and-drop-icon": "", @@ -4612,13 +4735,20 @@ "title-showing-partial-data": "", "title-unable-to-show-log-volume": "" }, + "logs-volumne-panel-list": { + "body-no-logs-volume-available": "" + }, "make-datasource-setup": { "aria-label-query": "" }, "next": "", "next-prev-result": { "aria-label-next": "", - "aria-label-prev": "" + "aria-label-prev": "", + "depth": "", + "depth-span-filter-matches": "", + "services": "", + "services-span-filter-matches": "" }, "no-data-source-call-to-action": { "cta-element": { @@ -4634,6 +4764,7 @@ "aria-label-go-queryless": "" }, "raw-list-container": { + "item-count": "", "label-expand-results": "" }, "raw-list-item": { @@ -4649,7 +4780,6 @@ "newest-first": "Nyaste först", "oldest-first": "Äldsta först", "query-history": "SQL-frågehistorik", - "query-library": "Frågebibliotek", "settings": "Inställningar", "starred": "Stjärnmärkt" }, @@ -4765,6 +4895,9 @@ }, "show-critical-path-only": "", "show-matches-only": "", + "span-bar": { + "tooltip-critical-path": "" + }, "span-bar-section": { "description-link": "", "title-span-bar": "" @@ -4791,6 +4924,7 @@ "aria-label-select-service-name-operator": "", "aria-label-select-span-name": "", "aria-label-select-span-name-operator": "", + "label-collapse": "", "label-duration": "", "label-service-name": "", "label-span-name": "", @@ -4834,6 +4968,9 @@ "tooltip-expand": "", "tooltip-expand-all": "" }, + "timeline-header-row": { + "title": "" + }, "toolbar": { "add-to-extensions": "Lägg till", "add-to-queryless-extensions": "Gå utan frågor", @@ -4863,7 +5000,8 @@ "title-share-thoughts-about-tracing-grafana": "" }, "trace-page-header": { - "text-partial-trace": "" + "text-partial-trace": "", + "tooltip-url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "", @@ -4877,6 +5015,7 @@ "title-trace": "" }, "unconnected-node-graph-container": { + "count-warning": "", "title-node-graph": "" }, "unthemed-logs": { @@ -4930,11 +5069,20 @@ "classic-conditions": { "label-conditions": "" }, + "condition": { + "of": "", + "to": "", + "when": "" + }, "expression-query-editor": { "label-operation": "" }, "math": { - "available-math-functions": "" + "available-math-functions": "", + "run-math-operations": "", + "tooltip-footer": "", + "tooltip-title": "", + "tooltip-trigger": "" }, "reduce": { "label-function": "", @@ -5013,6 +5161,12 @@ }, "essentials": { "title-essentials": "" + }, + "progress-bar": { + "steps-status": "" + }, + "progress-status": { + "your-progress": "" } }, "grafana": { @@ -5404,8 +5558,14 @@ }, "inspector": { "inspect-data-tab": { + "loading": "", "no-data": "" }, + "inspect-error-tab": { + "error-status-message": "", + "error-status-no-message": "", + "error-trace-message": "" + }, "inspect-jsontab": { "apply": "", "support": "" @@ -5420,7 +5580,11 @@ }, "query-inspector": { "query-inspector": "", - "text-loading-query-inspector": "" + "text-loading-query-inspector": "", + "count-frames_one": "", + "count-frames_other": "", + "count-rows_one": "", + "count-rows_other": "" } }, "invites": { @@ -5432,6 +5596,11 @@ "name": "" }, "signup-invited-page": { + "complete-following": "", + "custom-has-invited-you": "", + "default-has-invited-you": "", + "greeting-custom": "", + "greeting-default": "", "label-email": "", "label-name": "", "label-password": "", @@ -5627,6 +5796,11 @@ "has-connected-dashboards": { "dashboard-name": "" }, + "library-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_other": "" + }, "library-panels-search": { "placeholder-search-by-name-or-description": "" }, @@ -5653,12 +5827,15 @@ "success": "Bibliotekspanelen sparades" }, "save-library-panel-modal": { + "affected-dashboards": "", "cancel": "", "dashboard-name": "", "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "num-affected_one": "", + "num-affected_other": "" } }, "link": { @@ -5683,6 +5860,7 @@ "live": { "dashboard-changed-modal": { "continue-editing": "", + "description": "", "discard-local-changes": "", "title-dashboard-changed": "" }, @@ -5761,6 +5939,9 @@ "collapse": "", "expand": "" }, + "log-labels-list": { + "log-line": "" + }, "log-line-menu": { "copy-link": "Kopiera länk till loggrad", "copy-log": "Kopiera loggrad", @@ -5850,6 +6031,8 @@ "aria-label-no-details": "" }, "un-themed-log-details-row": { + "filter-out": "", + "filter-out-query": "", "title-copy-value-to-clipboard": "", "toggle-field-button": { "tooltip-field-instead-message": "", @@ -5857,6 +6040,10 @@ }, "tooltip-adhoc-statistics": "", "tooltip-hide-adhoc-statistics": "" + }, + "un-themed-log-label-stats": { + "field-log-stats": "", + "label-log-stats": "" } }, "manage-dashbaords": { @@ -5878,6 +6065,7 @@ "options": "" }, "import-dashboard-overview-un-connected": { + "importing-from": "", "published-by": "", "updated-on": "" }, @@ -5937,6 +6125,9 @@ "button": "Migrera den här instansen till molnet", "header": "Låt oss hantera din Grafana-stack" }, + "dashboard-info": { + "dashboard": "" + }, "delete-migration-token-confirm": { "body": "Om du redan har använt denna token för en självhanterad installation kan den installationen inte längre ladda upp innehåll.", "confirm-button": "Ta bort token", @@ -5952,6 +6143,7 @@ "title": "Frånkoppla från molnstack" }, "folder-info": { + "folder": "", "unable-to-load-folder": "" }, "get-started": { @@ -6574,6 +6766,7 @@ "org": { "new-org-page": { "create": "", + "description": "", "label-organization-name": "", "placeholder-org-name": "" }, @@ -6582,6 +6775,9 @@ "label-organization-profile": "", "update-organization-name": "" }, + "select-org-page": { + "description": "" + }, "user-invite-form": { "back": "", "label-email-or-username": "", @@ -6589,7 +6785,11 @@ "label-send-invite-email": "", "placeholder-optional": "", "role": "", - "submit": "" + "submit": "", + "tooltip": "" + }, + "user-invite-page": { + "sub-title": "" } }, "org-picker": { @@ -6632,7 +6832,9 @@ "title-not-found": "" }, "panel-renderer": { + "failed-to-load-plugin": "", "loading-plugin-panel": "", + "no-panel-component": "", "no-panel-data": "" }, "panel-type-card": { @@ -6659,7 +6861,14 @@ "select-placeholder": "" }, "playlist": { + "playlist-table-rows": { + "aria-label-playlist-item": "", + "multiple-dashboards-found": "", + "no-dashboards-found": "", + "not-found": "" + }, "start-modal": { + "button-start": "", "description-customize-dashboard-elements-visibility": "", "description-panel-heights-adjusted-screen": "", "label-autofit": "", @@ -6719,6 +6928,11 @@ "message": "Inga spellistor hittades" } }, + "plugin": { + "plugin-details-deprecated-warning": { + "body-deprecated": "" + } + }, "plugins": { "app-root-page": { "access-denied": { @@ -6822,6 +7036,12 @@ "add-new-data-source": "", "title-button-disabled": "" }, + "install-controls": { + "install": "", + "installing": "", + "update": "", + "updating": "" + }, "install-controls-button": { "title-uninstall-modal": "" }, @@ -6840,6 +7060,7 @@ "label-severity": "" }, "not-found-plugin": { + "body-plugin-not-found": "", "title-plugin-not-found": "" }, "plugin-actions": { @@ -6854,12 +7075,17 @@ "tooltip-plugin-deprecated-longer-receives-updates": "" }, "plugin-details-body": { + "needs-service-account": "", "page-not-found": "" }, "plugin-details-deprecated-warning": { "title-deprecated": "" }, + "plugin-details-header-dependencies": { + "grafana-dependency": "" + }, "plugin-details-signature": { + "body-invalid-plugin-signature": "", "read-more-about-plugins-signing": "", "title-invalid-plugin-signature": "" }, @@ -6890,6 +7116,8 @@ "update-available": "" }, "plugin-usage": { + "body-missing-feature-toggle-panel-title-search": "", + "num-usages": "", "title-missing-feature-toggle-panel-title-search": "", "title-not-used-yet": "" }, @@ -6906,7 +7134,9 @@ }, "version-list": { "grafana-dependency": "", + "installed-version": "", "last-updated": "", + "latest-compatible-version": "", "no-version-history-was-found": "", "version": "" } @@ -6929,6 +7159,12 @@ }, "change-theme": "Ändra tema", "enable-kiosk-mode": "", + "feature-toggle-page": { + "profile-not-enabled": "" + }, + "feature-toggles-age": { + "enable-in-config": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -7073,6 +7309,8 @@ "repository-config-exists-configuration": "" }, "file-status-page": { + "save": "", + "saving": "", "title-error-loading-file": "" }, "files-view": { @@ -7103,7 +7341,6 @@ }, "getting-started": { "alert-temporary-outage": "", - "engaging-graphic": "", "modal-description-public-access": "", "modal-description-required-features": "", "modal-title-set-up-public-access": "", @@ -7199,13 +7436,16 @@ "checked": "", "finished": "", "health": "", + "healthy": "", "job-id": "", "last-ref": "", "messages": "", + "not-available": "", "pull-status": "", "resources": "", "started": "", "status": "", + "unhealthy": "", "view-folder": "", "webhook": "", "webhook-events": "", @@ -7496,31 +7736,41 @@ "hidden": "", "query-name-div-title-edit-query-name": "" }, + "query-error-alert": { + "trace-id": "" + }, "query-group": { "add-query": "", "expression": "", "title-data-source-help": "" }, "query-group-options-editor": { + "collapsed-interval": "", + "collapsed-max-data-points": "", "hide-time-info": "", "Query options-title-query-options": "", "relative-time": "", + "relative-time-tooltip": "", "render-cache-timeout-option": { "cache-timeout": "" }, "render-interval-option": { "interval": "", + "interval-tooltip": "", "min-interval": "", + "min-interval-tooltip": "", "time-range-max-data-points": "" }, "render-max-data-points-option": { "max-data-points": "", + "max-data-points-tooltip": "", "width-of-panel": "" }, "render-query-caching-ttloption": { "cache-ttl": "" }, - "time-shift": "" + "time-shift": "", + "time-shift-tooltip": "" }, "query-group-top-section": { "data-source": "", @@ -7702,6 +7952,9 @@ } }, "search": { + "action-row": { + "panel-type": "" + }, "actions": { "include-panels": "Inkludera paneler", "remove-datasource-filter": "Datakälla: {{datasource}}", @@ -7825,6 +8078,9 @@ "label-roles": "", "used-by": "" }, + "service-account-profile-row": { + "edit": "" + }, "service-account-role-row": { "aria-label-role": "" }, @@ -8349,6 +8605,7 @@ "generate-enum-values-from-data": "" }, "enum-mapping-row": { + "click-to-edit": "", "remove-enum-row-aria-label-delete-enum-row": "", "remove-enum-row-tooltip-delete": "" }, @@ -8403,10 +8660,13 @@ "label-format": "", "label-set-timezone": "", "label-time-field": "", + "tooltip-format": "", "tooltip-timezone-manually": "" }, "get-tooltips": { - "json-value": "" + "description": "", + "json-value": "", + "valid-paths": "" }, "group-by-field-configuration": { "placeholder-ignored": "", @@ -8623,6 +8883,7 @@ "message": "Inga användare hittades" }, "token-revoked-modal": { + "auto-revoked": "", "resume-message": "", "sign-in": "", "title-you-have-been-automatically-signed-out": "" @@ -8717,7 +8978,8 @@ "general": "", "placeholder-descriptive-text": "", "placeholder-label-name": "", - "placeholder-variable-name": "" + "placeholder-variable-name": "", + "run-query": "" }, "variable-editor-list": { "definition": "", diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index ec527f99876..6f0c0d625ee 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -1,6 +1,5 @@ { "_comment": "Kod, İngilizce ifadeler için ana referans kaynağıdır. Bileşenlerde doğrudan güncellenmeli ve ek çoğul formlar bu dosyada belirtilmelidir.", - "{{pageStart}} - {{pageEnd}} of {{numPages}}": "", "access-control": { "add-permission": { "built-in-aria-label": "", @@ -969,7 +968,8 @@ "expression-result": { "aria-label-nextpage": "", "aria-label-previouspage": "", - "no-data": "" + "no-data": "", + "page-counter": "" }, "federated-rule-warning": { "experimental": "", @@ -2004,6 +2004,10 @@ "title": "Uyarı kuralları" }, "rulerrule-loading-error": "Kural yüklenemedi", + "toggle": { + "go-back-to-old-look": "", + "try-out-the-new-look": "" + }, "unknown-rule-type": "" }, "rule-list-errors": { @@ -2513,6 +2517,18 @@ "configuration-required": "", "refer-documentation-configure-authentication": "" }, + "fields": { + "allowed-groups-description": "", + "allowed-groups-description-oauth": "", + "api-url-description": "", + "team-ids-description": "", + "team-ids-description-oauth": "", + "team-ids-github": "", + "team-ids-other": "", + "teams-url-description": "", + "teams-url-description-oauth": "", + "use-pkce-description": "" + }, "provider-card": { "text-badge-enabled": "", "text-badge-not-enabled": "" @@ -2526,6 +2542,8 @@ "label-enabled": "", "reset-configuration": "", "reset-configuration-description": "", + "save": "", + "saving": "", "title-more-actions": "", "title-reset": "", "tooltip-more-actions": "" @@ -2671,6 +2689,9 @@ "canvas": { "not-found-display": { "not-found": "" + }, + "text-display": { + "double-click-to-set": "" } }, "carousel": { @@ -2947,7 +2968,9 @@ }, "annotation-settings-list": { "aria-label-delete": "", + "built-in": "", "data-source": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3060,6 +3083,7 @@ } }, "confirm-plugin-dashboard-save-modal": { + "body-plugin-dashboard": "", "cancel": "", "overwrite": "", "title-plugin-dashboard": "" @@ -3082,6 +3106,7 @@ } }, "dashboard-validation": { + "body-dashboard-failed-schema-validation": "", "title-checking-dashboard-validity": "", "title-dashboard-failed-schema-validation": "", "title-error-checking-dashboard-validity": "" @@ -3216,6 +3241,7 @@ }, "gen-aihistory": { "aria-label-send-custom-feedback": "", + "footer-text": "", "placeholder-tell-ai-what-to-do-next": "" }, "get-field-override-categories": { @@ -3306,9 +3332,13 @@ "make-editable": "" }, "minimalistic-pagination": { + "page-count": "", "tooltip-next": "", "tooltip-previous": "" }, + "new-dashboard-with-ds": { + "not-found": "" + }, "new-panel": { "configure-button": "", "configure-button-menu": "", @@ -3371,9 +3401,12 @@ "go-back-to-queries": "" }, "public-dashboard-not-available": { + "does-not-exist": "", + "paused": "", "try-again-later": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, "row-options-button": { @@ -3435,6 +3468,8 @@ "description": "", "label-copy-tags": "", "label-folder": "", + "save": "", + "saving": "", "title": "" }, "save-dashboard-button": { @@ -3451,19 +3486,30 @@ "label-details": "" }, "save-dashboard-error-proxy": { - "title-conflict": "" + "body-name-exists": "", + "body-version-mismatch": "", + "title-name-exists": "", + "title-version-mismatch": "" }, "save-dashboard-form": { "cancel": "", "label-current-range-dashboard-default": "", "label-current-variable-values-dashboard-default": "", "no-changes-to-save": "", - "placeholder-describe-changes": "" + "placeholder-describe-changes": "", + "save": "", + "saving": "" }, "save-provisioned-dashboard-form": { "cancel": "", + "cannot-be-saved": "", "copy-json-to-clipboard": "", - "save-json-to-file": "" + "file-path": "", + "save-json-to-file": "", + "see-docs": "" + }, + "share-public-dashboard-loader": { + "loading-configuration": "" }, "solo-panel": { "loading-initializing-dashboard": "", @@ -3642,6 +3688,9 @@ "title-delete": "" }, "transformation-picker": { + "info": "", + "info-graph-not-suitable": "", + "info-switch-to-table": "", "placeholder-search-for-transformation": "", "read-more": "", "title-transformations": "" @@ -3666,6 +3715,9 @@ "discard": "", "title-unsaved-changes": "" }, + "untheme-dashboard-row": { + "dashboard-datasource": "" + }, "unthemed-dashboard-row": { "aria-label-delete-row": "", "learn-more": "" @@ -3677,7 +3729,10 @@ "tags-expected-strings": "etiklet dizi hâlinde metinlerden oluşmalı" }, "version-history-comparison": { - "label-view-json-diff": "" + "button-restore": "", + "label-view-json-diff": "", + "new-updated-by": "", + "old-updated-by": "" }, "version-history-table": { "aria-label-toggle-selection": "", @@ -3776,8 +3831,10 @@ "title-annotation-support-source": "" }, "annotation-settings-list": { + "built-in": "", "data-source": "", "delete-aria-label": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3838,6 +3895,8 @@ }, "data-source-variable-form": { "data-source-options": "", + "description-instance-name-filter": "", + "example-instance-name-filter": "", "selection-options": "" }, "description-label": { @@ -3898,7 +3957,13 @@ "title-plugin-dashboard": "", "title-someone-else-has-updated-this-dashboard": "", "would-still-dashboard": "" - } + }, + "save-and-overwrite": "" + }, + "library-viz-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_other": "" }, "name-already-exists-error": { "body-name-already-exists": "", @@ -3908,6 +3973,7 @@ "alert": { "title-errors-loading-rules": "" }, + "error-failed-to-load": "", "text-loading-rules": "", "title-dashboard-not-saved": "" }, @@ -3937,7 +4003,10 @@ "title-close": "" }, "provisioned-delete-modal": { + "cannot-be-deleted": "", + "file-path": "", "ok": "", + "see-docs": "", "title-cannot-delete-provisioned-dashboard": "" }, "provisioned-resource-delete-modal": { @@ -3950,13 +4019,21 @@ "query": "" }, "query-variable-editor-form": { + "description-examples": "", + "description-optional": "", "label-data-source": "", "query-options": "", "selection-options": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, + "save-button": { + "save": "", + "save-and-overwrite": "", + "saving": "" + }, "save-dashboard-as-form": { "aria-label-save-dashboard-description-field": "", "aria-label-save-dashboard-title-field": "", @@ -4009,17 +4086,21 @@ "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "affected-dashboards_one": "", + "affected-dashboards_other": "" }, "save-provisioned-dashboard-form": { "api-error": "Pano kaydedilirken hata oluştu", "api-success": "Pano değişiklikleri kaydedildi", "cancel": "İptal", + "cannot-be-saved": "", "copy-json-message": "Hedefe doğrudan erişiminiz varsa JSON'u kopyalayıp oraya yapıştırın.", "copy-json-to-clipboard": "", "dashboard-comment-placeholder-describe-changes-optional": "Değişikliklerinizi açıklayan bir not ekleyin (isteğe bağlı)", "description-branch-name-in-git-hub": "GitHub'daki dal adı", "description-inside-repository": "Depo içindeki dosya yolu (.json veya .yaml)", + "file-path": "", "label-branch": "Dal", "label-comment": "Yorum", "label-description": "Açıklama", @@ -4030,6 +4111,7 @@ "save": "Kaydet", "save-json-to-file": "", "saving": "Kaydediliyor…", + "see-docs": "", "title-required": "Pano başlığı gereklidir", "title-same-as-folder": "Pano adı klasör adı ile aynı olamaz", "title-this-repository-is-read-only": "Bu depo salt okunur", @@ -4048,6 +4130,9 @@ "share-button": { "aria-label-sharedropdownmenu": "" }, + "solo-panel-page": { + "loading": "" + }, "text-box-variable-form": { "placeholder-default-value-if-any": "", "text-options": "" @@ -4107,10 +4192,18 @@ "preview-of-values": "", "show-more": "" }, + "version-history": { + "comparison": { + "button-restore": "" + } + }, "version-history-comparison": { - "label-view-json-diff": "" + "label-view-json-diff": "", + "new-version-updated": "", + "old-version-updated": "" }, "version-history-header": { + "compare-versions": "", "latest": "", "tooltip-reset-version": "" }, @@ -4286,6 +4379,7 @@ }, "button-row": { "delete": "", + "save-and-test": "", "test": "" }, "cloud-info-box": { @@ -4309,6 +4403,9 @@ "data-source-picker": { "aria-label-select-a-data-source": "" }, + "data-source-plugin-config-page": { + "page-not-found": "" + }, "data-source-plugin-state": { "plugin-state": "" }, @@ -4445,8 +4542,12 @@ } }, "explore": { + "accordian-logs": { + "footer": "" + }, "accordian-references": { - "references": "" + "references": "", + "view-linked-span": "" }, "add-to-dashboard": "Panoya ekle", "basic-extensions": { @@ -4455,6 +4556,7 @@ "clear": "", "confirm-navigation-modal": { "cancel": "", + "new-tab": "", "open": "", "open-in-new-tab": "" }, @@ -4475,9 +4577,14 @@ "add-transformation": "", "aria-label-delete-transformation": "", "aria-label-edit-transformation": "", + "body-correlation-details": "", + "expression": "", "label-description": "", + "label-description-header": "", "label-label": "", - "title-correlation-details": "" + "title-correlation-details": "", + "tooltip-transformations": "", + "transformations": "" }, "correlation-transformation-add-modal": { "add-transformation": "", @@ -4503,10 +4610,13 @@ "title-unsaved-changes-to-correlation": "" }, "draggable-manager-demo": { + "click-and-drag-gray-divider": "", "click-horizontally-somewhere-colored-below": "", + "drag-value": "", "draggable-manager-demo": "", "dragging-a-divider": "", - "dragging-a-sub-region": "" + "dragging-a-sub-region": "", + "value": "" }, "drilldownInfo": { "action": "Grafana Drilldown'a git", @@ -4537,6 +4647,10 @@ "outline": "", "tooltip-content-outline": "" }, + "feature-toggle-page": { + "description-explore-disabled": "", + "title-explore-disabled": "" + }, "get-matches-metadata": { "matches": "" }, @@ -4547,10 +4661,15 @@ }, "live-logs": { "clear-logs": "", - "exit-live-mode": "" + "exit-live-mode": "", + "last-line-received": "", + "pause": "", + "resume": "" }, "live-tail-button": { + "live": "", "pause-the-live-stream": "", + "paused": "", "start-live-stream-your-logs": "", "stop-and-exit-the-live-stream": "" }, @@ -4579,12 +4698,14 @@ "show-original-line": "" }, "logs-sample-panel": { + "label": "", "logs-sample-is-loading": "", "no-logs-sample-data": "", "open-in-split-view-button": { "open-logs-in-split-view": "" }, - "title-failed-sample-query": "" + "title-failed-sample-query": "", + "tooltip": "" }, "logs-table-available-fields": { "title-label-percentage": "" @@ -4593,7 +4714,9 @@ "no-fields": "" }, "logs-table-multi-select": { - "fields": "" + "fields": "", + "reset": "", + "selected-fields": "" }, "logs-table-nav-field": { "aria-label-drag-and-drop-icon": "", @@ -4612,13 +4735,20 @@ "title-showing-partial-data": "", "title-unable-to-show-log-volume": "" }, + "logs-volumne-panel-list": { + "body-no-logs-volume-available": "" + }, "make-datasource-setup": { "aria-label-query": "" }, "next": "", "next-prev-result": { "aria-label-next": "", - "aria-label-prev": "" + "aria-label-prev": "", + "depth": "", + "depth-span-filter-matches": "", + "services": "", + "services-span-filter-matches": "" }, "no-data-source-call-to-action": { "cta-element": { @@ -4634,6 +4764,7 @@ "aria-label-go-queryless": "" }, "raw-list-container": { + "item-count": "", "label-expand-results": "" }, "raw-list-item": { @@ -4649,7 +4780,6 @@ "newest-first": "Yeniden eskiye", "oldest-first": "Eskiden yeniye", "query-history": "Sorgu geçmişi", - "query-library": "Sorgu kitaplığı", "settings": "Ayarlar", "starred": "Yıldızlı" }, @@ -4765,6 +4895,9 @@ }, "show-critical-path-only": "", "show-matches-only": "", + "span-bar": { + "tooltip-critical-path": "" + }, "span-bar-section": { "description-link": "", "title-span-bar": "" @@ -4791,6 +4924,7 @@ "aria-label-select-service-name-operator": "", "aria-label-select-span-name": "", "aria-label-select-span-name-operator": "", + "label-collapse": "", "label-duration": "", "label-service-name": "", "label-span-name": "", @@ -4834,6 +4968,9 @@ "tooltip-expand": "", "tooltip-expand-all": "" }, + "timeline-header-row": { + "title": "" + }, "toolbar": { "add-to-extensions": "Ekle", "add-to-queryless-extensions": "Sorgu olmadan devam et", @@ -4863,7 +5000,8 @@ "title-share-thoughts-about-tracing-grafana": "" }, "trace-page-header": { - "text-partial-trace": "" + "text-partial-trace": "", + "tooltip-url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "", @@ -4877,6 +5015,7 @@ "title-trace": "" }, "unconnected-node-graph-container": { + "count-warning": "", "title-node-graph": "" }, "unthemed-logs": { @@ -4930,11 +5069,20 @@ "classic-conditions": { "label-conditions": "" }, + "condition": { + "of": "", + "to": "", + "when": "" + }, "expression-query-editor": { "label-operation": "" }, "math": { - "available-math-functions": "" + "available-math-functions": "", + "run-math-operations": "", + "tooltip-footer": "", + "tooltip-title": "", + "tooltip-trigger": "" }, "reduce": { "label-function": "", @@ -5013,6 +5161,12 @@ }, "essentials": { "title-essentials": "" + }, + "progress-bar": { + "steps-status": "" + }, + "progress-status": { + "your-progress": "" } }, "grafana": { @@ -5404,8 +5558,14 @@ }, "inspector": { "inspect-data-tab": { + "loading": "", "no-data": "" }, + "inspect-error-tab": { + "error-status-message": "", + "error-status-no-message": "", + "error-trace-message": "" + }, "inspect-jsontab": { "apply": "", "support": "" @@ -5420,7 +5580,11 @@ }, "query-inspector": { "query-inspector": "", - "text-loading-query-inspector": "" + "text-loading-query-inspector": "", + "count-frames_one": "", + "count-frames_other": "", + "count-rows_one": "", + "count-rows_other": "" } }, "invites": { @@ -5432,6 +5596,11 @@ "name": "" }, "signup-invited-page": { + "complete-following": "", + "custom-has-invited-you": "", + "default-has-invited-you": "", + "greeting-custom": "", + "greeting-default": "", "label-email": "", "label-name": "", "label-password": "", @@ -5627,6 +5796,11 @@ "has-connected-dashboards": { "dashboard-name": "" }, + "library-panel-info": { + "last-edited": "", + "usage-count_one": "", + "usage-count_other": "" + }, "library-panels-search": { "placeholder-search-by-name-or-description": "" }, @@ -5653,12 +5827,15 @@ "success": "Kütüphane paneli kaydedildi" }, "save-library-panel-modal": { + "affected-dashboards": "", "cancel": "", "dashboard-name": "", "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "num-affected_one": "", + "num-affected_other": "" } }, "link": { @@ -5683,6 +5860,7 @@ "live": { "dashboard-changed-modal": { "continue-editing": "", + "description": "", "discard-local-changes": "", "title-dashboard-changed": "" }, @@ -5761,6 +5939,9 @@ "collapse": "", "expand": "" }, + "log-labels-list": { + "log-line": "" + }, "log-line-menu": { "copy-link": "Bağlantıyı günlük satırına kopyala", "copy-log": "Günlük satırını kopyala", @@ -5850,6 +6031,8 @@ "aria-label-no-details": "" }, "un-themed-log-details-row": { + "filter-out": "", + "filter-out-query": "", "title-copy-value-to-clipboard": "", "toggle-field-button": { "tooltip-field-instead-message": "", @@ -5857,6 +6040,10 @@ }, "tooltip-adhoc-statistics": "", "tooltip-hide-adhoc-statistics": "" + }, + "un-themed-log-label-stats": { + "field-log-stats": "", + "label-log-stats": "" } }, "manage-dashbaords": { @@ -5878,6 +6065,7 @@ "options": "" }, "import-dashboard-overview-un-connected": { + "importing-from": "", "published-by": "", "updated-on": "" }, @@ -5937,6 +6125,9 @@ "button": "Bu oturumu Cloud'a taşıyın", "header": "Grafana yığınınızı yönetelim" }, + "dashboard-info": { + "dashboard": "" + }, "delete-migration-token-confirm": { "body": "Bu belirteci daha önce kendi kendine yönetilen bir kurulumda kullandıysanız o kurulum artık içerik yükleyemeyecektir.", "confirm-button": "Belirteci sil", @@ -5952,6 +6143,7 @@ "title": "Bulut yığını ile bağlantıyı kes" }, "folder-info": { + "folder": "", "unable-to-load-folder": "" }, "get-started": { @@ -6574,6 +6766,7 @@ "org": { "new-org-page": { "create": "", + "description": "", "label-organization-name": "", "placeholder-org-name": "" }, @@ -6582,6 +6775,9 @@ "label-organization-profile": "", "update-organization-name": "" }, + "select-org-page": { + "description": "" + }, "user-invite-form": { "back": "", "label-email-or-username": "", @@ -6589,7 +6785,11 @@ "label-send-invite-email": "", "placeholder-optional": "", "role": "", - "submit": "" + "submit": "", + "tooltip": "" + }, + "user-invite-page": { + "sub-title": "" } }, "org-picker": { @@ -6632,7 +6832,9 @@ "title-not-found": "" }, "panel-renderer": { + "failed-to-load-plugin": "", "loading-plugin-panel": "", + "no-panel-component": "", "no-panel-data": "" }, "panel-type-card": { @@ -6659,7 +6861,14 @@ "select-placeholder": "" }, "playlist": { + "playlist-table-rows": { + "aria-label-playlist-item": "", + "multiple-dashboards-found": "", + "no-dashboards-found": "", + "not-found": "" + }, "start-modal": { + "button-start": "", "description-customize-dashboard-elements-visibility": "", "description-panel-heights-adjusted-screen": "", "label-autofit": "", @@ -6719,6 +6928,11 @@ "message": "Oynatma listesi bulunamadı" } }, + "plugin": { + "plugin-details-deprecated-warning": { + "body-deprecated": "" + } + }, "plugins": { "app-root-page": { "access-denied": { @@ -6822,6 +7036,12 @@ "add-new-data-source": "", "title-button-disabled": "" }, + "install-controls": { + "install": "", + "installing": "", + "update": "", + "updating": "" + }, "install-controls-button": { "title-uninstall-modal": "" }, @@ -6840,6 +7060,7 @@ "label-severity": "" }, "not-found-plugin": { + "body-plugin-not-found": "", "title-plugin-not-found": "" }, "plugin-actions": { @@ -6854,12 +7075,17 @@ "tooltip-plugin-deprecated-longer-receives-updates": "" }, "plugin-details-body": { + "needs-service-account": "", "page-not-found": "" }, "plugin-details-deprecated-warning": { "title-deprecated": "" }, + "plugin-details-header-dependencies": { + "grafana-dependency": "" + }, "plugin-details-signature": { + "body-invalid-plugin-signature": "", "read-more-about-plugins-signing": "", "title-invalid-plugin-signature": "" }, @@ -6890,6 +7116,8 @@ "update-available": "" }, "plugin-usage": { + "body-missing-feature-toggle-panel-title-search": "", + "num-usages": "", "title-missing-feature-toggle-panel-title-search": "", "title-not-used-yet": "" }, @@ -6906,7 +7134,9 @@ }, "version-list": { "grafana-dependency": "", + "installed-version": "", "last-updated": "", + "latest-compatible-version": "", "no-version-history-was-found": "", "version": "" } @@ -6929,6 +7159,12 @@ }, "change-theme": "Temayı değiştir", "enable-kiosk-mode": "", + "feature-toggle-page": { + "profile-not-enabled": "" + }, + "feature-toggles-age": { + "enable-in-config": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -7073,6 +7309,8 @@ "repository-config-exists-configuration": "" }, "file-status-page": { + "save": "", + "saving": "", "title-error-loading-file": "" }, "files-view": { @@ -7103,7 +7341,6 @@ }, "getting-started": { "alert-temporary-outage": "", - "engaging-graphic": "", "modal-description-public-access": "", "modal-description-required-features": "", "modal-title-set-up-public-access": "", @@ -7199,13 +7436,16 @@ "checked": "", "finished": "", "health": "", + "healthy": "", "job-id": "", "last-ref": "", "messages": "", + "not-available": "", "pull-status": "", "resources": "", "started": "", "status": "", + "unhealthy": "", "view-folder": "", "webhook": "", "webhook-events": "", @@ -7496,31 +7736,41 @@ "hidden": "", "query-name-div-title-edit-query-name": "" }, + "query-error-alert": { + "trace-id": "" + }, "query-group": { "add-query": "", "expression": "", "title-data-source-help": "" }, "query-group-options-editor": { + "collapsed-interval": "", + "collapsed-max-data-points": "", "hide-time-info": "", "Query options-title-query-options": "", "relative-time": "", + "relative-time-tooltip": "", "render-cache-timeout-option": { "cache-timeout": "" }, "render-interval-option": { "interval": "", + "interval-tooltip": "", "min-interval": "", + "min-interval-tooltip": "", "time-range-max-data-points": "" }, "render-max-data-points-option": { "max-data-points": "", + "max-data-points-tooltip": "", "width-of-panel": "" }, "render-query-caching-ttloption": { "cache-ttl": "" }, - "time-shift": "" + "time-shift": "", + "time-shift-tooltip": "" }, "query-group-top-section": { "data-source": "", @@ -7702,6 +7952,9 @@ } }, "search": { + "action-row": { + "panel-type": "" + }, "actions": { "include-panels": "Panelleri dâhil et", "remove-datasource-filter": "Veri kaynağı: {{datasource}}", @@ -7825,6 +8078,9 @@ "label-roles": "", "used-by": "" }, + "service-account-profile-row": { + "edit": "" + }, "service-account-role-row": { "aria-label-role": "" }, @@ -8349,6 +8605,7 @@ "generate-enum-values-from-data": "" }, "enum-mapping-row": { + "click-to-edit": "", "remove-enum-row-aria-label-delete-enum-row": "", "remove-enum-row-tooltip-delete": "" }, @@ -8403,10 +8660,13 @@ "label-format": "", "label-set-timezone": "", "label-time-field": "", + "tooltip-format": "", "tooltip-timezone-manually": "" }, "get-tooltips": { - "json-value": "" + "description": "", + "json-value": "", + "valid-paths": "" }, "group-by-field-configuration": { "placeholder-ignored": "", @@ -8623,6 +8883,7 @@ "message": "Kullanıcı bulunamadı" }, "token-revoked-modal": { + "auto-revoked": "", "resume-message": "", "sign-in": "", "title-you-have-been-automatically-signed-out": "" @@ -8717,7 +8978,8 @@ "general": "", "placeholder-descriptive-text": "", "placeholder-label-name": "", - "placeholder-variable-name": "" + "placeholder-variable-name": "", + "run-query": "" }, "variable-editor-list": { "definition": "", diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 4639605a695..00a8ae11080 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -1,6 +1,5 @@ { "_comment": "代码是英语短语的真实来源。应直接在组件中对它们进行更新,并在此文件中指定其他复数形式。", - "{{pageStart}} - {{pageEnd}} of {{numPages}}": "", "access-control": { "add-permission": { "built-in-aria-label": "", @@ -963,7 +962,8 @@ "expression-result": { "aria-label-nextpage": "", "aria-label-previouspage": "", - "no-data": "" + "no-data": "", + "page-counter": "" }, "federated-rule-warning": { "experimental": "", @@ -1994,6 +1994,10 @@ "title": "" }, "rulerrule-loading-error": "加载规则失败", + "toggle": { + "go-back-to-old-look": "", + "try-out-the-new-look": "" + }, "unknown-rule-type": "" }, "rule-list-errors": { @@ -2500,6 +2504,18 @@ "configuration-required": "", "refer-documentation-configure-authentication": "" }, + "fields": { + "allowed-groups-description": "", + "allowed-groups-description-oauth": "", + "api-url-description": "", + "team-ids-description": "", + "team-ids-description-oauth": "", + "team-ids-github": "", + "team-ids-other": "", + "teams-url-description": "", + "teams-url-description-oauth": "", + "use-pkce-description": "" + }, "provider-card": { "text-badge-enabled": "", "text-badge-not-enabled": "" @@ -2513,6 +2529,8 @@ "label-enabled": "", "reset-configuration": "", "reset-configuration-description": "", + "save": "", + "saving": "", "title-more-actions": "", "title-reset": "", "tooltip-more-actions": "" @@ -2653,6 +2671,9 @@ "canvas": { "not-found-display": { "not-found": "" + }, + "text-display": { + "double-click-to-set": "" } }, "carousel": { @@ -2929,7 +2950,9 @@ }, "annotation-settings-list": { "aria-label-delete": "", + "built-in": "", "data-source": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3042,6 +3065,7 @@ } }, "confirm-plugin-dashboard-save-modal": { + "body-plugin-dashboard": "", "cancel": "", "overwrite": "", "title-plugin-dashboard": "" @@ -3064,6 +3088,7 @@ } }, "dashboard-validation": { + "body-dashboard-failed-schema-validation": "", "title-checking-dashboard-validity": "", "title-dashboard-failed-schema-validation": "", "title-error-checking-dashboard-validity": "" @@ -3198,6 +3223,7 @@ }, "gen-aihistory": { "aria-label-send-custom-feedback": "", + "footer-text": "", "placeholder-tell-ai-what-to-do-next": "" }, "get-field-override-categories": { @@ -3288,9 +3314,13 @@ "make-editable": "" }, "minimalistic-pagination": { + "page-count": "", "tooltip-next": "", "tooltip-previous": "" }, + "new-dashboard-with-ds": { + "not-found": "" + }, "new-panel": { "configure-button": "", "configure-button-menu": "", @@ -3352,9 +3382,12 @@ "go-back-to-queries": "" }, "public-dashboard-not-available": { + "does-not-exist": "", + "paused": "", "try-again-later": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, "row-options-button": { @@ -3416,6 +3449,8 @@ "description": "", "label-copy-tags": "", "label-folder": "", + "save": "", + "saving": "", "title": "" }, "save-dashboard-button": { @@ -3432,19 +3467,30 @@ "label-details": "" }, "save-dashboard-error-proxy": { - "title-conflict": "" + "body-name-exists": "", + "body-version-mismatch": "", + "title-name-exists": "", + "title-version-mismatch": "" }, "save-dashboard-form": { "cancel": "", "label-current-range-dashboard-default": "", "label-current-variable-values-dashboard-default": "", "no-changes-to-save": "", - "placeholder-describe-changes": "" + "placeholder-describe-changes": "", + "save": "", + "saving": "" }, "save-provisioned-dashboard-form": { "cancel": "", + "cannot-be-saved": "", "copy-json-to-clipboard": "", - "save-json-to-file": "" + "file-path": "", + "save-json-to-file": "", + "see-docs": "" + }, + "share-public-dashboard-loader": { + "loading-configuration": "" }, "solo-panel": { "loading-initializing-dashboard": "", @@ -3623,6 +3669,9 @@ "title-delete": "" }, "transformation-picker": { + "info": "", + "info-graph-not-suitable": "", + "info-switch-to-table": "", "placeholder-search-for-transformation": "", "read-more": "", "title-transformations": "" @@ -3647,6 +3696,9 @@ "discard": "", "title-unsaved-changes": "" }, + "untheme-dashboard-row": { + "dashboard-datasource": "" + }, "unthemed-dashboard-row": { "aria-label-delete-row": "", "learn-more": "" @@ -3658,7 +3710,10 @@ "tags-expected-strings": "标签预期字符串数组" }, "version-history-comparison": { - "label-view-json-diff": "" + "button-restore": "", + "label-view-json-diff": "", + "new-updated-by": "", + "old-updated-by": "" }, "version-history-table": { "aria-label-toggle-selection": "", @@ -3757,8 +3812,10 @@ "title-annotation-support-source": "" }, "annotation-settings-list": { + "built-in": "", "data-source": "", "delete-aria-label": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3819,6 +3876,8 @@ }, "data-source-variable-form": { "data-source-options": "", + "description-instance-name-filter": "", + "example-instance-name-filter": "", "selection-options": "" }, "description-label": { @@ -3879,7 +3938,12 @@ "title-plugin-dashboard": "", "title-someone-else-has-updated-this-dashboard": "", "would-still-dashboard": "" - } + }, + "save-and-overwrite": "" + }, + "library-viz-panel-info": { + "last-edited": "", + "usage-count_other": "" }, "name-already-exists-error": { "body-name-already-exists": "", @@ -3889,6 +3953,7 @@ "alert": { "title-errors-loading-rules": "" }, + "error-failed-to-load": "", "text-loading-rules": "", "title-dashboard-not-saved": "" }, @@ -3918,7 +3983,10 @@ "title-close": "" }, "provisioned-delete-modal": { + "cannot-be-deleted": "", + "file-path": "", "ok": "", + "see-docs": "", "title-cannot-delete-provisioned-dashboard": "" }, "provisioned-resource-delete-modal": { @@ -3931,13 +3999,21 @@ "query": "" }, "query-variable-editor-form": { + "description-examples": "", + "description-optional": "", "label-data-source": "", "query-options": "", "selection-options": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, + "save-button": { + "save": "", + "save-and-overwrite": "", + "saving": "" + }, "save-dashboard-as-form": { "aria-label-save-dashboard-description-field": "", "aria-label-save-dashboard-title-field": "", @@ -3990,17 +4066,20 @@ "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "affected-dashboards_other": "" }, "save-provisioned-dashboard-form": { "api-error": "保存数据面板时出错", "api-success": "数据面板更改已保存", "cancel": "", + "cannot-be-saved": "", "copy-json-message": "如果您可以直接访问目标,请复制 JSON 并将其粘贴到那里。", "copy-json-to-clipboard": "", "dashboard-comment-placeholder-describe-changes-optional": "添加备注以描述您的更改(可选)", "description-branch-name-in-git-hub": "GitHub 中的分支名称", "description-inside-repository": "存储库内的文件路径(.json 或 .yaml)", + "file-path": "", "label-branch": "分支", "label-comment": "评论", "label-description": "", @@ -4011,6 +4090,7 @@ "save": "", "save-json-to-file": "", "saving": "正在保存...", + "see-docs": "", "title-required": "数据面板标题为必填项", "title-same-as-folder": "数据面板名称不能与文件夹名称相同", "title-this-repository-is-read-only": "此存储库为只读", @@ -4029,6 +4109,9 @@ "share-button": { "aria-label-sharedropdownmenu": "" }, + "solo-panel-page": { + "loading": "" + }, "text-box-variable-form": { "placeholder-default-value-if-any": "", "text-options": "" @@ -4088,10 +4171,18 @@ "preview-of-values": "", "show-more": "" }, + "version-history": { + "comparison": { + "button-restore": "" + } + }, "version-history-comparison": { - "label-view-json-diff": "" + "label-view-json-diff": "", + "new-version-updated": "", + "old-version-updated": "" }, "version-history-header": { + "compare-versions": "", "latest": "", "tooltip-reset-version": "" }, @@ -4267,6 +4358,7 @@ }, "button-row": { "delete": "", + "save-and-test": "", "test": "" }, "cloud-info-box": { @@ -4290,6 +4382,9 @@ "data-source-picker": { "aria-label-select-a-data-source": "" }, + "data-source-plugin-config-page": { + "page-not-found": "" + }, "data-source-plugin-state": { "plugin-state": "" }, @@ -4426,8 +4521,12 @@ } }, "explore": { + "accordian-logs": { + "footer": "" + }, "accordian-references": { - "references": "" + "references": "", + "view-linked-span": "" }, "add-to-dashboard": "添加到仪表板", "basic-extensions": { @@ -4436,6 +4535,7 @@ "clear": "", "confirm-navigation-modal": { "cancel": "", + "new-tab": "", "open": "", "open-in-new-tab": "" }, @@ -4456,9 +4556,14 @@ "add-transformation": "", "aria-label-delete-transformation": "", "aria-label-edit-transformation": "", + "body-correlation-details": "", + "expression": "", "label-description": "", + "label-description-header": "", "label-label": "", - "title-correlation-details": "" + "title-correlation-details": "", + "tooltip-transformations": "", + "transformations": "" }, "correlation-transformation-add-modal": { "add-transformation": "", @@ -4484,10 +4589,13 @@ "title-unsaved-changes-to-correlation": "" }, "draggable-manager-demo": { + "click-and-drag-gray-divider": "", "click-horizontally-somewhere-colored-below": "", + "drag-value": "", "draggable-manager-demo": "", "dragging-a-divider": "", - "dragging-a-sub-region": "" + "dragging-a-sub-region": "", + "value": "" }, "drilldownInfo": { "action": "前往 Grafana Drilldown", @@ -4518,6 +4626,10 @@ "outline": "", "tooltip-content-outline": "" }, + "feature-toggle-page": { + "description-explore-disabled": "", + "title-explore-disabled": "" + }, "get-matches-metadata": { "matches": "" }, @@ -4528,10 +4640,15 @@ }, "live-logs": { "clear-logs": "", - "exit-live-mode": "" + "exit-live-mode": "", + "last-line-received": "", + "pause": "", + "resume": "" }, "live-tail-button": { + "live": "", "pause-the-live-stream": "", + "paused": "", "start-live-stream-your-logs": "", "stop-and-exit-the-live-stream": "" }, @@ -4560,12 +4677,14 @@ "show-original-line": "" }, "logs-sample-panel": { + "label": "", "logs-sample-is-loading": "", "no-logs-sample-data": "", "open-in-split-view-button": { "open-logs-in-split-view": "" }, - "title-failed-sample-query": "" + "title-failed-sample-query": "", + "tooltip": "" }, "logs-table-available-fields": { "title-label-percentage": "" @@ -4574,7 +4693,9 @@ "no-fields": "" }, "logs-table-multi-select": { - "fields": "" + "fields": "", + "reset": "", + "selected-fields": "" }, "logs-table-nav-field": { "aria-label-drag-and-drop-icon": "", @@ -4593,13 +4714,20 @@ "title-showing-partial-data": "", "title-unable-to-show-log-volume": "" }, + "logs-volumne-panel-list": { + "body-no-logs-volume-available": "" + }, "make-datasource-setup": { "aria-label-query": "" }, "next": "", "next-prev-result": { "aria-label-next": "", - "aria-label-prev": "" + "aria-label-prev": "", + "depth": "", + "depth-span-filter-matches": "", + "services": "", + "services-span-filter-matches": "" }, "no-data-source-call-to-action": { "cta-element": { @@ -4615,6 +4743,7 @@ "aria-label-go-queryless": "" }, "raw-list-container": { + "item-count": "", "label-expand-results": "" }, "raw-list-item": { @@ -4630,7 +4759,6 @@ "newest-first": "最新在前", "oldest-first": "最早在前", "query-history": "查询历史记录", - "query-library": "查询库", "settings": "设置", "starred": "已加星标" }, @@ -4746,6 +4874,9 @@ }, "show-critical-path-only": "", "show-matches-only": "", + "span-bar": { + "tooltip-critical-path": "" + }, "span-bar-section": { "description-link": "", "title-span-bar": "" @@ -4772,6 +4903,7 @@ "aria-label-select-service-name-operator": "", "aria-label-select-span-name": "", "aria-label-select-span-name-operator": "", + "label-collapse": "", "label-duration": "", "label-service-name": "", "label-span-name": "", @@ -4815,6 +4947,9 @@ "tooltip-expand": "", "tooltip-expand-all": "" }, + "timeline-header-row": { + "title": "" + }, "toolbar": { "add-to-extensions": "", "add-to-queryless-extensions": "转到无查询", @@ -4844,7 +4979,8 @@ "title-share-thoughts-about-tracing-grafana": "" }, "trace-page-header": { - "text-partial-trace": "" + "text-partial-trace": "", + "tooltip-url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "", @@ -4858,6 +4994,7 @@ "title-trace": "" }, "unconnected-node-graph-container": { + "count-warning": "", "title-node-graph": "" }, "unthemed-logs": { @@ -4911,11 +5048,20 @@ "classic-conditions": { "label-conditions": "" }, + "condition": { + "of": "", + "to": "", + "when": "" + }, "expression-query-editor": { "label-operation": "" }, "math": { - "available-math-functions": "" + "available-math-functions": "", + "run-math-operations": "", + "tooltip-footer": "", + "tooltip-title": "", + "tooltip-trigger": "" }, "reduce": { "label-function": "", @@ -4994,6 +5140,12 @@ }, "essentials": { "title-essentials": "" + }, + "progress-bar": { + "steps-status": "" + }, + "progress-status": { + "your-progress": "" } }, "grafana": { @@ -5385,8 +5537,14 @@ }, "inspector": { "inspect-data-tab": { + "loading": "", "no-data": "" }, + "inspect-error-tab": { + "error-status-message": "", + "error-status-no-message": "", + "error-trace-message": "" + }, "inspect-jsontab": { "apply": "", "support": "" @@ -5401,7 +5559,9 @@ }, "query-inspector": { "query-inspector": "", - "text-loading-query-inspector": "" + "text-loading-query-inspector": "", + "count-frames_other": "", + "count-rows_other": "" } }, "invites": { @@ -5413,6 +5573,11 @@ "name": "" }, "signup-invited-page": { + "complete-following": "", + "custom-has-invited-you": "", + "default-has-invited-you": "", + "greeting-custom": "", + "greeting-default": "", "label-email": "", "label-name": "", "label-password": "", @@ -5608,6 +5773,10 @@ "has-connected-dashboards": { "dashboard-name": "" }, + "library-panel-info": { + "last-edited": "", + "usage-count_other": "" + }, "library-panels-search": { "placeholder-search-by-name-or-description": "" }, @@ -5633,12 +5802,14 @@ "success": "库面板已保存" }, "save-library-panel-modal": { + "affected-dashboards": "", "cancel": "", "dashboard-name": "", "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "num-affected_other": "" } }, "link": { @@ -5663,6 +5834,7 @@ "live": { "dashboard-changed-modal": { "continue-editing": "", + "description": "", "discard-local-changes": "", "title-dashboard-changed": "" }, @@ -5741,6 +5913,9 @@ "collapse": "", "expand": "" }, + "log-labels-list": { + "log-line": "" + }, "log-line-menu": { "copy-link": "将链接复制到日志行", "copy-log": "复制日志行", @@ -5830,6 +6005,8 @@ "aria-label-no-details": "" }, "un-themed-log-details-row": { + "filter-out": "", + "filter-out-query": "", "title-copy-value-to-clipboard": "", "toggle-field-button": { "tooltip-field-instead-message": "", @@ -5837,6 +6014,10 @@ }, "tooltip-adhoc-statistics": "", "tooltip-hide-adhoc-statistics": "" + }, + "un-themed-log-label-stats": { + "field-log-stats": "", + "label-log-stats": "" } }, "manage-dashbaords": { @@ -5858,6 +6039,7 @@ "options": "" }, "import-dashboard-overview-un-connected": { + "importing-from": "", "published-by": "", "updated-on": "" }, @@ -5917,6 +6099,9 @@ "button": "将此实例迁移到云端", "header": "让我们管理你的 Grafana 堆栈" }, + "dashboard-info": { + "dashboard": "" + }, "delete-migration-token-confirm": { "body": "", "confirm-button": "删除令牌", @@ -5932,6 +6117,7 @@ "title": "断开云堆栈连接" }, "folder-info": { + "folder": "", "unable-to-load-folder": "" }, "get-started": { @@ -6554,6 +6740,7 @@ "org": { "new-org-page": { "create": "", + "description": "", "label-organization-name": "", "placeholder-org-name": "" }, @@ -6562,6 +6749,9 @@ "label-organization-profile": "", "update-organization-name": "" }, + "select-org-page": { + "description": "" + }, "user-invite-form": { "back": "", "label-email-or-username": "", @@ -6569,7 +6759,11 @@ "label-send-invite-email": "", "placeholder-optional": "", "role": "", - "submit": "" + "submit": "", + "tooltip": "" + }, + "user-invite-page": { + "sub-title": "" } }, "org-picker": { @@ -6612,7 +6806,9 @@ "title-not-found": "" }, "panel-renderer": { + "failed-to-load-plugin": "", "loading-plugin-panel": "", + "no-panel-component": "", "no-panel-data": "" }, "panel-type-card": { @@ -6639,7 +6835,14 @@ "select-placeholder": "" }, "playlist": { + "playlist-table-rows": { + "aria-label-playlist-item": "", + "multiple-dashboards-found": "", + "no-dashboards-found": "", + "not-found": "" + }, "start-modal": { + "button-start": "", "description-customize-dashboard-elements-visibility": "", "description-panel-heights-adjusted-screen": "", "label-autofit": "", @@ -6699,6 +6902,11 @@ "message": "找不到播放列表" } }, + "plugin": { + "plugin-details-deprecated-warning": { + "body-deprecated": "" + } + }, "plugins": { "app-root-page": { "access-denied": { @@ -6802,6 +7010,12 @@ "add-new-data-source": "", "title-button-disabled": "" }, + "install-controls": { + "install": "", + "installing": "", + "update": "", + "updating": "" + }, "install-controls-button": { "title-uninstall-modal": "" }, @@ -6820,6 +7034,7 @@ "label-severity": "" }, "not-found-plugin": { + "body-plugin-not-found": "", "title-plugin-not-found": "" }, "plugin-actions": { @@ -6834,12 +7049,17 @@ "tooltip-plugin-deprecated-longer-receives-updates": "" }, "plugin-details-body": { + "needs-service-account": "", "page-not-found": "" }, "plugin-details-deprecated-warning": { "title-deprecated": "" }, + "plugin-details-header-dependencies": { + "grafana-dependency": "" + }, "plugin-details-signature": { + "body-invalid-plugin-signature": "", "read-more-about-plugins-signing": "", "title-invalid-plugin-signature": "" }, @@ -6870,6 +7090,8 @@ "update-available": "" }, "plugin-usage": { + "body-missing-feature-toggle-panel-title-search": "", + "num-usages": "", "title-missing-feature-toggle-panel-title-search": "", "title-not-used-yet": "" }, @@ -6886,7 +7108,9 @@ }, "version-list": { "grafana-dependency": "", + "installed-version": "", "last-updated": "", + "latest-compatible-version": "", "no-version-history-was-found": "", "version": "" } @@ -6909,6 +7133,12 @@ }, "change-theme": "", "enable-kiosk-mode": "", + "feature-toggle-page": { + "profile-not-enabled": "" + }, + "feature-toggles-age": { + "enable-in-config": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -7052,6 +7282,8 @@ "repository-config-exists-configuration": "" }, "file-status-page": { + "save": "", + "saving": "", "title-error-loading-file": "" }, "files-view": { @@ -7082,7 +7314,6 @@ }, "getting-started": { "alert-temporary-outage": "", - "engaging-graphic": "", "modal-description-public-access": "", "modal-description-required-features": "", "modal-title-set-up-public-access": "", @@ -7178,13 +7409,16 @@ "checked": "", "finished": "", "health": "", + "healthy": "", "job-id": "", "last-ref": "", "messages": "", + "not-available": "", "pull-status": "", "resources": "", "started": "", "status": "", + "unhealthy": "", "view-folder": "", "webhook": "", "webhook-events": "", @@ -7474,31 +7708,41 @@ "hidden": "", "query-name-div-title-edit-query-name": "" }, + "query-error-alert": { + "trace-id": "" + }, "query-group": { "add-query": "", "expression": "", "title-data-source-help": "" }, "query-group-options-editor": { + "collapsed-interval": "", + "collapsed-max-data-points": "", "hide-time-info": "", "Query options-title-query-options": "", "relative-time": "", + "relative-time-tooltip": "", "render-cache-timeout-option": { "cache-timeout": "" }, "render-interval-option": { "interval": "", + "interval-tooltip": "", "min-interval": "", + "min-interval-tooltip": "", "time-range-max-data-points": "" }, "render-max-data-points-option": { "max-data-points": "", + "max-data-points-tooltip": "", "width-of-panel": "" }, "render-query-caching-ttloption": { "cache-ttl": "" }, - "time-shift": "" + "time-shift": "", + "time-shift-tooltip": "" }, "query-group-top-section": { "data-source": "", @@ -7677,6 +7921,9 @@ } }, "search": { + "action-row": { + "panel-type": "" + }, "actions": { "include-panels": "包括面板", "remove-datasource-filter": "数据源:{{datasource}}", @@ -7800,6 +8047,9 @@ "label-roles": "", "used-by": "" }, + "service-account-profile-row": { + "edit": "" + }, "service-account-role-row": { "aria-label-role": "" }, @@ -8324,6 +8574,7 @@ "generate-enum-values-from-data": "" }, "enum-mapping-row": { + "click-to-edit": "", "remove-enum-row-aria-label-delete-enum-row": "", "remove-enum-row-tooltip-delete": "" }, @@ -8378,10 +8629,13 @@ "label-format": "", "label-set-timezone": "", "label-time-field": "", + "tooltip-format": "", "tooltip-timezone-manually": "" }, "get-tooltips": { - "json-value": "" + "description": "", + "json-value": "", + "valid-paths": "" }, "group-by-field-configuration": { "placeholder-ignored": "", @@ -8598,6 +8852,7 @@ "message": "找不到用户" }, "token-revoked-modal": { + "auto-revoked": "", "resume-message": "", "sign-in": "", "title-you-have-been-automatically-signed-out": "" @@ -8692,7 +8947,8 @@ "general": "", "placeholder-descriptive-text": "", "placeholder-label-name": "", - "placeholder-variable-name": "" + "placeholder-variable-name": "", + "run-query": "" }, "variable-editor-list": { "definition": "", diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index 139b5e6fb3d..f9d0cdf2425 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -1,6 +1,5 @@ { "_comment": "代碼是英文短語的真實來源。它們應直接在元件中更新,並在此檔案中指定其他複數形式。", - "{{pageStart}} - {{pageEnd}} of {{numPages}}": "", "access-control": { "add-permission": { "built-in-aria-label": "", @@ -963,7 +962,8 @@ "expression-result": { "aria-label-nextpage": "", "aria-label-previouspage": "", - "no-data": "" + "no-data": "", + "page-counter": "" }, "federated-rule-warning": { "experimental": "", @@ -1994,6 +1994,10 @@ "title": "警報規則" }, "rulerrule-loading-error": "無法載入規則", + "toggle": { + "go-back-to-old-look": "", + "try-out-the-new-look": "" + }, "unknown-rule-type": "" }, "rule-list-errors": { @@ -2500,6 +2504,18 @@ "configuration-required": "", "refer-documentation-configure-authentication": "" }, + "fields": { + "allowed-groups-description": "", + "allowed-groups-description-oauth": "", + "api-url-description": "", + "team-ids-description": "", + "team-ids-description-oauth": "", + "team-ids-github": "", + "team-ids-other": "", + "teams-url-description": "", + "teams-url-description-oauth": "", + "use-pkce-description": "" + }, "provider-card": { "text-badge-enabled": "", "text-badge-not-enabled": "" @@ -2513,6 +2529,8 @@ "label-enabled": "", "reset-configuration": "", "reset-configuration-description": "", + "save": "", + "saving": "", "title-more-actions": "", "title-reset": "", "tooltip-more-actions": "" @@ -2653,6 +2671,9 @@ "canvas": { "not-found-display": { "not-found": "" + }, + "text-display": { + "double-click-to-set": "" } }, "carousel": { @@ -2929,7 +2950,9 @@ }, "annotation-settings-list": { "aria-label-delete": "", + "built-in": "", "data-source": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3042,6 +3065,7 @@ } }, "confirm-plugin-dashboard-save-modal": { + "body-plugin-dashboard": "", "cancel": "", "overwrite": "", "title-plugin-dashboard": "" @@ -3064,6 +3088,7 @@ } }, "dashboard-validation": { + "body-dashboard-failed-schema-validation": "", "title-checking-dashboard-validity": "", "title-dashboard-failed-schema-validation": "", "title-error-checking-dashboard-validity": "" @@ -3198,6 +3223,7 @@ }, "gen-aihistory": { "aria-label-send-custom-feedback": "", + "footer-text": "", "placeholder-tell-ai-what-to-do-next": "" }, "get-field-override-categories": { @@ -3288,9 +3314,13 @@ "make-editable": "" }, "minimalistic-pagination": { + "page-count": "", "tooltip-next": "", "tooltip-previous": "" }, + "new-dashboard-with-ds": { + "not-found": "" + }, "new-panel": { "configure-button": "", "configure-button-menu": "", @@ -3352,9 +3382,12 @@ "go-back-to-queries": "" }, "public-dashboard-not-available": { + "does-not-exist": "", + "paused": "", "try-again-later": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, "row-options-button": { @@ -3416,6 +3449,8 @@ "description": "", "label-copy-tags": "", "label-folder": "", + "save": "", + "saving": "", "title": "" }, "save-dashboard-button": { @@ -3432,19 +3467,30 @@ "label-details": "" }, "save-dashboard-error-proxy": { - "title-conflict": "" + "body-name-exists": "", + "body-version-mismatch": "", + "title-name-exists": "", + "title-version-mismatch": "" }, "save-dashboard-form": { "cancel": "", "label-current-range-dashboard-default": "", "label-current-variable-values-dashboard-default": "", "no-changes-to-save": "", - "placeholder-describe-changes": "" + "placeholder-describe-changes": "", + "save": "", + "saving": "" }, "save-provisioned-dashboard-form": { "cancel": "", + "cannot-be-saved": "", "copy-json-to-clipboard": "", - "save-json-to-file": "" + "file-path": "", + "save-json-to-file": "", + "see-docs": "" + }, + "share-public-dashboard-loader": { + "loading-configuration": "" }, "solo-panel": { "loading-initializing-dashboard": "", @@ -3623,6 +3669,9 @@ "title-delete": "" }, "transformation-picker": { + "info": "", + "info-graph-not-suitable": "", + "info-switch-to-table": "", "placeholder-search-for-transformation": "", "read-more": "", "title-transformations": "" @@ -3647,6 +3696,9 @@ "discard": "", "title-unsaved-changes": "" }, + "untheme-dashboard-row": { + "dashboard-datasource": "" + }, "unthemed-dashboard-row": { "aria-label-delete-row": "", "learn-more": "" @@ -3658,7 +3710,10 @@ "tags-expected-strings": "標籤預期字串陣列" }, "version-history-comparison": { - "label-view-json-diff": "" + "button-restore": "", + "label-view-json-diff": "", + "new-updated-by": "", + "old-updated-by": "" }, "version-history-table": { "aria-label-toggle-selection": "", @@ -3757,8 +3812,10 @@ "title-annotation-support-source": "" }, "annotation-settings-list": { + "built-in": "", "data-source": "", "delete-aria-label": "", + "disabled": "", "new-query": "", "query-name": "", "tooltip-move-down": "", @@ -3819,6 +3876,8 @@ }, "data-source-variable-form": { "data-source-options": "", + "description-instance-name-filter": "", + "example-instance-name-filter": "", "selection-options": "" }, "description-label": { @@ -3879,7 +3938,12 @@ "title-plugin-dashboard": "", "title-someone-else-has-updated-this-dashboard": "", "would-still-dashboard": "" - } + }, + "save-and-overwrite": "" + }, + "library-viz-panel-info": { + "last-edited": "", + "usage-count_other": "" }, "name-already-exists-error": { "body-name-already-exists": "", @@ -3889,6 +3953,7 @@ "alert": { "title-errors-loading-rules": "" }, + "error-failed-to-load": "", "text-loading-rules": "", "title-dashboard-not-saved": "" }, @@ -3918,7 +3983,10 @@ "title-close": "" }, "provisioned-delete-modal": { + "cannot-be-deleted": "", + "file-path": "", "ok": "", + "see-docs": "", "title-cannot-delete-provisioned-dashboard": "" }, "provisioned-resource-delete-modal": { @@ -3931,13 +3999,21 @@ "query": "" }, "query-variable-editor-form": { + "description-examples": "", + "description-optional": "", "label-data-source": "", "query-options": "", "selection-options": "" }, "revert-dashboard-modal": { + "body-restore-version": "", "title-restore-version": "" }, + "save-button": { + "save": "", + "save-and-overwrite": "", + "saving": "" + }, "save-dashboard-as-form": { "aria-label-save-dashboard-description-field": "", "aria-label-save-dashboard-title-field": "", @@ -3990,17 +4066,20 @@ "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "affected-dashboards_other": "" }, "save-provisioned-dashboard-form": { "api-error": "儲存儀表板時發生錯誤", "api-success": "儀表板變更已儲存", "cancel": "取消", + "cannot-be-saved": "", "copy-json-message": "如果您可以直接存取目標,請複製 JSON 並將其貼上至該處。", "copy-json-to-clipboard": "", "dashboard-comment-placeholder-describe-changes-optional": "新增備註以描述您的變更(選填)", "description-branch-name-in-git-hub": "GitHub 中的分支名稱", "description-inside-repository": "存放庫中的檔案路徑 (.json 或 .yaml)", + "file-path": "", "label-branch": "分支", "label-comment": "評論", "label-description": "說明", @@ -4011,6 +4090,7 @@ "save": "儲存", "save-json-to-file": "", "saving": "正在儲存…", + "see-docs": "", "title-required": "儀表板標題為必填", "title-same-as-folder": "儀表板名稱不能與資料夾名稱相同", "title-this-repository-is-read-only": "此存放庫為唯讀", @@ -4029,6 +4109,9 @@ "share-button": { "aria-label-sharedropdownmenu": "" }, + "solo-panel-page": { + "loading": "" + }, "text-box-variable-form": { "placeholder-default-value-if-any": "", "text-options": "" @@ -4088,10 +4171,18 @@ "preview-of-values": "", "show-more": "" }, + "version-history": { + "comparison": { + "button-restore": "" + } + }, "version-history-comparison": { - "label-view-json-diff": "" + "label-view-json-diff": "", + "new-version-updated": "", + "old-version-updated": "" }, "version-history-header": { + "compare-versions": "", "latest": "", "tooltip-reset-version": "" }, @@ -4267,6 +4358,7 @@ }, "button-row": { "delete": "", + "save-and-test": "", "test": "" }, "cloud-info-box": { @@ -4290,6 +4382,9 @@ "data-source-picker": { "aria-label-select-a-data-source": "" }, + "data-source-plugin-config-page": { + "page-not-found": "" + }, "data-source-plugin-state": { "plugin-state": "" }, @@ -4426,8 +4521,12 @@ } }, "explore": { + "accordian-logs": { + "footer": "" + }, "accordian-references": { - "references": "" + "references": "", + "view-linked-span": "" }, "add-to-dashboard": "新增至儀表板", "basic-extensions": { @@ -4436,6 +4535,7 @@ "clear": "", "confirm-navigation-modal": { "cancel": "", + "new-tab": "", "open": "", "open-in-new-tab": "" }, @@ -4456,9 +4556,14 @@ "add-transformation": "", "aria-label-delete-transformation": "", "aria-label-edit-transformation": "", + "body-correlation-details": "", + "expression": "", "label-description": "", + "label-description-header": "", "label-label": "", - "title-correlation-details": "" + "title-correlation-details": "", + "tooltip-transformations": "", + "transformations": "" }, "correlation-transformation-add-modal": { "add-transformation": "", @@ -4484,10 +4589,13 @@ "title-unsaved-changes-to-correlation": "" }, "draggable-manager-demo": { + "click-and-drag-gray-divider": "", "click-horizontally-somewhere-colored-below": "", + "drag-value": "", "draggable-manager-demo": "", "dragging-a-divider": "", - "dragging-a-sub-region": "" + "dragging-a-sub-region": "", + "value": "" }, "drilldownInfo": { "action": "前往 Grafana Drilldown", @@ -4518,6 +4626,10 @@ "outline": "", "tooltip-content-outline": "" }, + "feature-toggle-page": { + "description-explore-disabled": "", + "title-explore-disabled": "" + }, "get-matches-metadata": { "matches": "" }, @@ -4528,10 +4640,15 @@ }, "live-logs": { "clear-logs": "", - "exit-live-mode": "" + "exit-live-mode": "", + "last-line-received": "", + "pause": "", + "resume": "" }, "live-tail-button": { + "live": "", "pause-the-live-stream": "", + "paused": "", "start-live-stream-your-logs": "", "stop-and-exit-the-live-stream": "" }, @@ -4560,12 +4677,14 @@ "show-original-line": "" }, "logs-sample-panel": { + "label": "", "logs-sample-is-loading": "", "no-logs-sample-data": "", "open-in-split-view-button": { "open-logs-in-split-view": "" }, - "title-failed-sample-query": "" + "title-failed-sample-query": "", + "tooltip": "" }, "logs-table-available-fields": { "title-label-percentage": "" @@ -4574,7 +4693,9 @@ "no-fields": "" }, "logs-table-multi-select": { - "fields": "" + "fields": "", + "reset": "", + "selected-fields": "" }, "logs-table-nav-field": { "aria-label-drag-and-drop-icon": "", @@ -4593,13 +4714,20 @@ "title-showing-partial-data": "", "title-unable-to-show-log-volume": "" }, + "logs-volumne-panel-list": { + "body-no-logs-volume-available": "" + }, "make-datasource-setup": { "aria-label-query": "" }, "next": "", "next-prev-result": { "aria-label-next": "", - "aria-label-prev": "" + "aria-label-prev": "", + "depth": "", + "depth-span-filter-matches": "", + "services": "", + "services-span-filter-matches": "" }, "no-data-source-call-to-action": { "cta-element": { @@ -4615,6 +4743,7 @@ "aria-label-go-queryless": "" }, "raw-list-container": { + "item-count": "", "label-expand-results": "" }, "raw-list-item": { @@ -4630,7 +4759,6 @@ "newest-first": "最新排前", "oldest-first": "最舊排前", "query-history": "查詢歷史記錄", - "query-library": "查詢資料庫", "settings": "設定", "starred": "已加星號" }, @@ -4746,6 +4874,9 @@ }, "show-critical-path-only": "", "show-matches-only": "", + "span-bar": { + "tooltip-critical-path": "" + }, "span-bar-section": { "description-link": "", "title-span-bar": "" @@ -4772,6 +4903,7 @@ "aria-label-select-service-name-operator": "", "aria-label-select-span-name": "", "aria-label-select-span-name-operator": "", + "label-collapse": "", "label-duration": "", "label-service-name": "", "label-span-name": "", @@ -4815,6 +4947,9 @@ "tooltip-expand": "", "tooltip-expand-all": "" }, + "timeline-header-row": { + "title": "" + }, "toolbar": { "add-to-extensions": "新增", "add-to-queryless-extensions": "停用查詢", @@ -4844,7 +4979,8 @@ "title-share-thoughts-about-tracing-grafana": "" }, "trace-page-header": { - "text-partial-trace": "" + "text-partial-trace": "", + "tooltip-url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "", @@ -4858,6 +4994,7 @@ "title-trace": "" }, "unconnected-node-graph-container": { + "count-warning": "", "title-node-graph": "" }, "unthemed-logs": { @@ -4911,11 +5048,20 @@ "classic-conditions": { "label-conditions": "" }, + "condition": { + "of": "", + "to": "", + "when": "" + }, "expression-query-editor": { "label-operation": "" }, "math": { - "available-math-functions": "" + "available-math-functions": "", + "run-math-operations": "", + "tooltip-footer": "", + "tooltip-title": "", + "tooltip-trigger": "" }, "reduce": { "label-function": "", @@ -4994,6 +5140,12 @@ }, "essentials": { "title-essentials": "" + }, + "progress-bar": { + "steps-status": "" + }, + "progress-status": { + "your-progress": "" } }, "grafana": { @@ -5385,8 +5537,14 @@ }, "inspector": { "inspect-data-tab": { + "loading": "", "no-data": "" }, + "inspect-error-tab": { + "error-status-message": "", + "error-status-no-message": "", + "error-trace-message": "" + }, "inspect-jsontab": { "apply": "", "support": "" @@ -5401,7 +5559,9 @@ }, "query-inspector": { "query-inspector": "", - "text-loading-query-inspector": "" + "text-loading-query-inspector": "", + "count-frames_other": "", + "count-rows_other": "" } }, "invites": { @@ -5413,6 +5573,11 @@ "name": "" }, "signup-invited-page": { + "complete-following": "", + "custom-has-invited-you": "", + "default-has-invited-you": "", + "greeting-custom": "", + "greeting-default": "", "label-email": "", "label-name": "", "label-password": "", @@ -5608,6 +5773,10 @@ "has-connected-dashboards": { "dashboard-name": "" }, + "library-panel-info": { + "last-edited": "", + "usage-count_other": "" + }, "library-panels-search": { "placeholder-search-by-name-or-description": "" }, @@ -5633,12 +5802,14 @@ "success": "資料庫面板已儲存" }, "save-library-panel-modal": { + "affected-dashboards": "", "cancel": "", "dashboard-name": "", "discard": "", "loading-connected-dashboards": "", "placeholder-search-affected-dashboards": "", - "update-all": "" + "update-all": "", + "num-affected_other": "" } }, "link": { @@ -5663,6 +5834,7 @@ "live": { "dashboard-changed-modal": { "continue-editing": "", + "description": "", "discard-local-changes": "", "title-dashboard-changed": "" }, @@ -5741,6 +5913,9 @@ "collapse": "", "expand": "" }, + "log-labels-list": { + "log-line": "" + }, "log-line-menu": { "copy-link": "將連結複製到日誌行", "copy-log": "複製日誌行", @@ -5830,6 +6005,8 @@ "aria-label-no-details": "" }, "un-themed-log-details-row": { + "filter-out": "", + "filter-out-query": "", "title-copy-value-to-clipboard": "", "toggle-field-button": { "tooltip-field-instead-message": "", @@ -5837,6 +6014,10 @@ }, "tooltip-adhoc-statistics": "", "tooltip-hide-adhoc-statistics": "" + }, + "un-themed-log-label-stats": { + "field-log-stats": "", + "label-log-stats": "" } }, "manage-dashbaords": { @@ -5858,6 +6039,7 @@ "options": "" }, "import-dashboard-overview-un-connected": { + "importing-from": "", "published-by": "", "updated-on": "" }, @@ -5917,6 +6099,9 @@ "button": "將此執行個體移轉至雲端", "header": "讓我們來管理您的 Grafana 堆疊" }, + "dashboard-info": { + "dashboard": "" + }, "delete-migration-token-confirm": { "body": "若您已在自我管理安裝中使用此權杖,則該安裝將無法再上傳內容。", "confirm-button": "刪除權杖", @@ -5932,6 +6117,7 @@ "title": "從雲端堆疊取消連線" }, "folder-info": { + "folder": "", "unable-to-load-folder": "" }, "get-started": { @@ -6554,6 +6740,7 @@ "org": { "new-org-page": { "create": "", + "description": "", "label-organization-name": "", "placeholder-org-name": "" }, @@ -6562,6 +6749,9 @@ "label-organization-profile": "", "update-organization-name": "" }, + "select-org-page": { + "description": "" + }, "user-invite-form": { "back": "", "label-email-or-username": "", @@ -6569,7 +6759,11 @@ "label-send-invite-email": "", "placeholder-optional": "", "role": "", - "submit": "" + "submit": "", + "tooltip": "" + }, + "user-invite-page": { + "sub-title": "" } }, "org-picker": { @@ -6612,7 +6806,9 @@ "title-not-found": "" }, "panel-renderer": { + "failed-to-load-plugin": "", "loading-plugin-panel": "", + "no-panel-component": "", "no-panel-data": "" }, "panel-type-card": { @@ -6639,7 +6835,14 @@ "select-placeholder": "" }, "playlist": { + "playlist-table-rows": { + "aria-label-playlist-item": "", + "multiple-dashboards-found": "", + "no-dashboards-found": "", + "not-found": "" + }, "start-modal": { + "button-start": "", "description-customize-dashboard-elements-visibility": "", "description-panel-heights-adjusted-screen": "", "label-autofit": "", @@ -6699,6 +6902,11 @@ "message": "未找到播放清單" } }, + "plugin": { + "plugin-details-deprecated-warning": { + "body-deprecated": "" + } + }, "plugins": { "app-root-page": { "access-denied": { @@ -6802,6 +7010,12 @@ "add-new-data-source": "", "title-button-disabled": "" }, + "install-controls": { + "install": "", + "installing": "", + "update": "", + "updating": "" + }, "install-controls-button": { "title-uninstall-modal": "" }, @@ -6820,6 +7034,7 @@ "label-severity": "" }, "not-found-plugin": { + "body-plugin-not-found": "", "title-plugin-not-found": "" }, "plugin-actions": { @@ -6834,12 +7049,17 @@ "tooltip-plugin-deprecated-longer-receives-updates": "" }, "plugin-details-body": { + "needs-service-account": "", "page-not-found": "" }, "plugin-details-deprecated-warning": { "title-deprecated": "" }, + "plugin-details-header-dependencies": { + "grafana-dependency": "" + }, "plugin-details-signature": { + "body-invalid-plugin-signature": "", "read-more-about-plugins-signing": "", "title-invalid-plugin-signature": "" }, @@ -6870,6 +7090,8 @@ "update-available": "" }, "plugin-usage": { + "body-missing-feature-toggle-panel-title-search": "", + "num-usages": "", "title-missing-feature-toggle-panel-title-search": "", "title-not-used-yet": "" }, @@ -6886,7 +7108,9 @@ }, "version-list": { "grafana-dependency": "", + "installed-version": "", "last-updated": "", + "latest-compatible-version": "", "no-version-history-was-found": "", "version": "" } @@ -6909,6 +7133,12 @@ }, "change-theme": "變更主題", "enable-kiosk-mode": "", + "feature-toggle-page": { + "profile-not-enabled": "" + }, + "feature-toggles-age": { + "enable-in-config": "" + }, "user-organizations": { "text-loading-organizations": "" }, @@ -7052,6 +7282,8 @@ "repository-config-exists-configuration": "" }, "file-status-page": { + "save": "", + "saving": "", "title-error-loading-file": "" }, "files-view": { @@ -7082,7 +7314,6 @@ }, "getting-started": { "alert-temporary-outage": "", - "engaging-graphic": "", "modal-description-public-access": "", "modal-description-required-features": "", "modal-title-set-up-public-access": "", @@ -7178,13 +7409,16 @@ "checked": "", "finished": "", "health": "", + "healthy": "", "job-id": "", "last-ref": "", "messages": "", + "not-available": "", "pull-status": "", "resources": "", "started": "", "status": "", + "unhealthy": "", "view-folder": "", "webhook": "", "webhook-events": "", @@ -7474,31 +7708,41 @@ "hidden": "", "query-name-div-title-edit-query-name": "" }, + "query-error-alert": { + "trace-id": "" + }, "query-group": { "add-query": "", "expression": "", "title-data-source-help": "" }, "query-group-options-editor": { + "collapsed-interval": "", + "collapsed-max-data-points": "", "hide-time-info": "", "Query options-title-query-options": "", "relative-time": "", + "relative-time-tooltip": "", "render-cache-timeout-option": { "cache-timeout": "" }, "render-interval-option": { "interval": "", + "interval-tooltip": "", "min-interval": "", + "min-interval-tooltip": "", "time-range-max-data-points": "" }, "render-max-data-points-option": { "max-data-points": "", + "max-data-points-tooltip": "", "width-of-panel": "" }, "render-query-caching-ttloption": { "cache-ttl": "" }, - "time-shift": "" + "time-shift": "", + "time-shift-tooltip": "" }, "query-group-top-section": { "data-source": "", @@ -7677,6 +7921,9 @@ } }, "search": { + "action-row": { + "panel-type": "" + }, "actions": { "include-panels": "包含面板", "remove-datasource-filter": "資料來源:{{datasource}}", @@ -7800,6 +8047,9 @@ "label-roles": "", "used-by": "" }, + "service-account-profile-row": { + "edit": "" + }, "service-account-role-row": { "aria-label-role": "" }, @@ -8324,6 +8574,7 @@ "generate-enum-values-from-data": "" }, "enum-mapping-row": { + "click-to-edit": "", "remove-enum-row-aria-label-delete-enum-row": "", "remove-enum-row-tooltip-delete": "" }, @@ -8378,10 +8629,13 @@ "label-format": "", "label-set-timezone": "", "label-time-field": "", + "tooltip-format": "", "tooltip-timezone-manually": "" }, "get-tooltips": { - "json-value": "" + "description": "", + "json-value": "", + "valid-paths": "" }, "group-by-field-configuration": { "placeholder-ignored": "", @@ -8598,6 +8852,7 @@ "message": "沒有找到使用者" }, "token-revoked-modal": { + "auto-revoked": "", "resume-message": "", "sign-in": "", "title-you-have-been-automatically-signed-out": "" @@ -8692,7 +8947,8 @@ "general": "", "placeholder-descriptive-text": "", "placeholder-label-name": "", - "placeholder-variable-name": "" + "placeholder-variable-name": "", + "run-query": "" }, "variable-editor-list": { "definition": "", From b887e8aa058539df56845791baa58d4e81db54f0 Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Tue, 22 Apr 2025 20:29:05 -0600 Subject: [PATCH 030/146] K8s: Dashboards: Add fine grained access control checks to /apis (#104347) --------- Co-authored-by: Ieva Co-authored-by: Gabriel MABILLE Co-authored-by: Marco de Abreu Co-authored-by: Georges Chaudy --- pkg/apimachinery/identity/context.go | 2 +- pkg/registry/apis/dashboard/authorizer.go | 79 + .../apis/dashboard/legacy/sql_dashboards.go | 18 - pkg/registry/apis/dashboard/legacy/storage.go | 6 + pkg/registry/apis/dashboard/legacy_storage.go | 9 +- pkg/registry/apis/dashboard/register.go | 38 +- pkg/services/authz/rbac/service.go | 9 +- pkg/services/authz/rbac/service_test.go | 97 +- .../integration/api_validation_test.go | 1420 ++++++++++++++++- scripts/grafana-server/custom.ini | 3 + 10 files changed, 1647 insertions(+), 34 deletions(-) create mode 100644 pkg/registry/apis/dashboard/authorizer.go diff --git a/pkg/apimachinery/identity/context.go b/pkg/apimachinery/identity/context.go index 68b63762ce6..08a3ed192b8 100644 --- a/pkg/apimachinery/identity/context.go +++ b/pkg/apimachinery/identity/context.go @@ -58,7 +58,7 @@ func newInternalIdentity(name string, namespace string, orgID int64) Requester { // This is useful for background tasks that has to communicate with unfied storage. It also returns a Requester with // static permissions so it can be used in legacy code paths. func WithServiceIdentity(ctx context.Context, orgID int64) (context.Context, Requester) { - r := newInternalIdentity(serviceName, "", orgID) + r := newInternalIdentity(serviceName, "*", orgID) return WithRequester(ctx, r), r } diff --git a/pkg/registry/apis/dashboard/authorizer.go b/pkg/registry/apis/dashboard/authorizer.go new file mode 100644 index 00000000000..fffb01da544 --- /dev/null +++ b/pkg/registry/apis/dashboard/authorizer.go @@ -0,0 +1,79 @@ +package dashboard + +import ( + "context" + + "k8s.io/apiserver/pkg/authorization/authorizer" + + "github.com/grafana/authlib/types" + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/dashboards" +) + +func GetAuthorizer(ac accesscontrol.AccessControl, l log.Logger) authorizer.Authorizer { + return authorizer.AuthorizerFunc( + func(ctx context.Context, attr authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) { + // Note that we will return Allow more than expected. + // This is because we do NOT want to hit the RoleAuthorizer that would be evaluated afterwards. + + if !attr.IsResourceRequest() { + return authorizer.DecisionDeny, "unexpected non-resource request", nil + } + + user, err := identity.GetRequester(ctx) + if err != nil { + return authorizer.DecisionDeny, "error getting requester", err + } + + ns := attr.GetNamespace() + if ns == "" { + return authorizer.DecisionDeny, "expected namespace", nil + } + + info, err := types.ParseNamespace(attr.GetNamespace()) + if err != nil { + return authorizer.DecisionDeny, "error reading org from namespace", err + } + + // Validate organization access before we possibly step out here. + if user.GetOrgID() != info.OrgID { + return authorizer.DecisionDeny, "org mismatch", dashboards.ErrUserIsNotSignedInToOrg + } + + switch attr.GetVerb() { + case "list", "search": + // Detailed read permissions are handled by authz, this just checks whether the user can ready *any* dashboard + ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsRead)) + if !ok || err != nil { + return authorizer.DecisionDeny, "can not read any dashboards", err + } + case "create": + // Detailed create permissions are handled by authz, this just checks whether the user can create *any* dashboard + ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsCreate)) + if !ok || err != nil { + return authorizer.DecisionDeny, "can not create any dashboards", err + } + case "get": + ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsRead, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(attr.GetName()))) + if !ok || err != nil { + return authorizer.DecisionDeny, "can not view dashboard", err + } + case "update", "patch": + ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(attr.GetName()))) + if !ok || err != nil { + return authorizer.DecisionDeny, "can not edit dashboard", err + } + case "delete": + ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsDelete, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(attr.GetName()))) + if !ok || err != nil { + return authorizer.DecisionDeny, "can not delete dashboard", err + } + default: + l.Info("unknown verb", "verb", attr.GetVerb()) + return authorizer.DecisionDeny, "unsupported verb", nil // Unknown verb + } + return authorizer.DecisionAllow, "", nil + }) +} diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go index 90012b46ff5..e7067e82b95 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go @@ -122,11 +122,6 @@ func (a *dashboardSqlAccess) getRows(ctx context.Context, sql *legacysql.LegacyD rows: rows, a: a, history: query.GetHistory, - // This looks up rules from the permissions on a user - canReadDashboard: func(scopes ...string) bool { - return true // ??? - }, - // accesscontrol.Checker(user, dashboards.ActionDashboardsRead), }, err } @@ -138,8 +133,6 @@ type rowsWrapper struct { history bool count int - canReadDashboard func(scopes ...string) bool - // Current row *dashboardRow err error @@ -180,17 +173,6 @@ func (r *rowsWrapper) Next() bool { } if r.row != nil { - d := r.row - - // Access control checker - scopes := []string{dashboards.ScopeDashboardsProvider.GetResourceScopeUID(d.Dash.Name)} - if d.FolderUID != "" { // Copied from searchV2... not sure the logic is right - scopes = append(scopes, dashboards.ScopeFoldersProvider.GetResourceScopeUID(d.FolderUID)) - } - if !r.canReadDashboard(scopes...) { - continue - } - // returns the first visible dashboard return true } diff --git a/pkg/registry/apis/dashboard/legacy/storage.go b/pkg/registry/apis/dashboard/legacy/storage.go index a51241a6410..89254fb0f2f 100644 --- a/pkg/registry/apis/dashboard/legacy/storage.go +++ b/pkg/registry/apis/dashboard/legacy/storage.go @@ -212,6 +212,12 @@ func (a *dashboardSqlAccess) ReadResource(ctx context.Context, req *resource.Rea Code: http.StatusNotFound, } } else { + meta, err := utils.MetaAccessor(dash) + if err != nil { + rsp.Error = resource.AsErrorResult(err) + } + rsp.Folder = meta.GetFolder() + rsp.Value, err = json.Marshal(dash) if err != nil { rsp.Error = resource.AsErrorResult(err) diff --git a/pkg/registry/apis/dashboard/legacy_storage.go b/pkg/registry/apis/dashboard/legacy_storage.go index 89390c18854..7fb9d54340e 100644 --- a/pkg/registry/apis/dashboard/legacy_storage.go +++ b/pkg/registry/apis/dashboard/legacy_storage.go @@ -10,6 +10,8 @@ import ( "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest" + "github.com/grafana/authlib/types" + "github.com/grafana/grafana/pkg/apimachinery/utils" grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" @@ -25,10 +27,11 @@ type DashboardStorage struct { DashboardService dashboards.DashboardService } -func (s *DashboardStorage) NewStore(dash utils.ResourceInfo, scheme *runtime.Scheme, defaultOptsGetter generic.RESTOptionsGetter, reg prometheus.Registerer, permissions dashboards.PermissionsRegistrationService) (grafanarest.Storage, error) { +func (s *DashboardStorage) NewStore(dash utils.ResourceInfo, scheme *runtime.Scheme, defaultOptsGetter generic.RESTOptionsGetter, reg prometheus.Registerer, permissions dashboards.PermissionsRegistrationService, ac types.AccessClient) (grafanarest.Storage, error) { server, err := resource.NewResourceServer(resource.ResourceServerOptions{ - Backend: s.Access, - Reg: reg, + Backend: s.Access, + Reg: reg, + AccessClient: ac, }) if err != nil { return nil, err diff --git a/pkg/registry/apis/dashboard/register.go b/pkg/registry/apis/dashboard/register.go index a2f4e524821..85a334dc405 100644 --- a/pkg/registry/apis/dashboard/register.go +++ b/pkg/registry/apis/dashboard/register.go @@ -12,6 +12,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" "k8s.io/kube-openapi/pkg/common" @@ -71,6 +72,7 @@ type DashboardsAPIBuilder struct { features featuremgmt.FeatureToggles accessControl accesscontrol.AccessControl + accessClient claims.AccessClient legacy *DashboardStorage unified resource.ResourceClient dashboardProvisioningService dashboards.DashboardProvisioningService @@ -97,6 +99,7 @@ func RegisterAPIService( provisioningDashboardService dashboards.DashboardProvisioningService, dashboardPermissions dashboards.PermissionsRegistrationService, accessControl accesscontrol.AccessControl, + accessClient claims.AccessClient, provisioning provisioning.ProvisioningService, dashStore dashboards.Store, reg prometheus.Registerer, @@ -121,6 +124,7 @@ func RegisterAPIService( dashboardPermissions: dashboardPermissions, features: features, accessControl: accessControl, + accessClient: accessClient, unified: unified, dashboardProvisioningService: provisioningDashboardService, search: NewSearchHandler(tracing, dual, legacyDashboardSearcher, unified, features), @@ -328,7 +332,16 @@ func (b *DashboardsAPIBuilder) validateUpdate(ctx context.Context, a admission.A } // Validate folder existence if specified and changed - if !a.IsDryRun() && newAccessor.GetFolder() != "" && newAccessor.GetFolder() != oldAccessor.GetFolder() { + if !a.IsDryRun() && newAccessor.GetFolder() != oldAccessor.GetFolder() { + id, err := identity.GetRequester(ctx) + if err != nil { + return fmt.Errorf("error getting requester: %w", err) + } + + if err := b.verifyFolderAccessPermissions(ctx, id, newAccessor.GetFolder()); err != nil { + return err + } + if err := b.validateFolderExists(ctx, newAccessor.GetFolder(), nsInfo.OrgID); err != nil { return apierrors.NewNotFound(folders.FolderResourceInfo.GroupResource(), newAccessor.GetFolder()) } @@ -472,7 +485,7 @@ func (b *DashboardsAPIBuilder) storageForVersion( storage := map[string]rest.Storage{} apiGroupInfo.VersionedResourcesStorageMap[dashboards.GroupVersion().Version] = storage - legacyStore, err := b.legacy.NewStore(dashboards, opts.Scheme, opts.OptsGetter, b.reg, b.dashboardPermissions) + legacyStore, err := b.legacy.NewStore(dashboards, opts.Scheme, opts.OptsGetter, b.reg, b.dashboardPermissions, b.accessClient) if err != nil { return err } @@ -535,3 +548,24 @@ func (b *DashboardsAPIBuilder) GetAPIRoutes(gv schema.GroupVersion) *builder.API defs := b.GetOpenAPIDefinitions()(func(path string) spec.Ref { return spec.Ref{} }) return b.search.GetAPIRoutes(defs) } + +func (b *DashboardsAPIBuilder) GetAuthorizer() authorizer.Authorizer { + return GetAuthorizer(b.accessControl, b.log) +} + +func (b *DashboardsAPIBuilder) verifyFolderAccessPermissions(ctx context.Context, user identity.Requester, folderIds ...string) error { + scopes := []string{} + for _, folderId := range folderIds { + scopes = append(scopes, dashboards.ScopeFoldersProvider.GetResourceScopeUID(folderId)) + } + ok, err := b.accessControl.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, scopes...)) + if err != nil { + return err + } + + if !ok { + return dashboards.ErrFolderAccessDenied + } + + return nil +} diff --git a/pkg/services/authz/rbac/service.go b/pkg/services/authz/rbac/service.go index f805197d292..e2d98a5bf9d 100644 --- a/pkg/services/authz/rbac/service.go +++ b/pkg/services/authz/rbac/service.go @@ -18,6 +18,7 @@ import ( authzv1 "github.com/grafana/authlib/authz/proto/v1" "github.com/grafana/authlib/cache" "github.com/grafana/authlib/types" + "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" @@ -553,10 +554,14 @@ func (s *Service) checkPermission(ctx context.Context, scopeMap map[string]bool, ctxLogger := s.logger.FromContext(ctx) // Only check action if the request doesn't specify scope - if req.Name == "" { + if req.Name == "" && req.Verb != utils.VerbCreate { return len(scopeMap) > 0, nil } + if req.Verb == utils.VerbCreate && req.ParentFolder == "" { + req.ParentFolder = accesscontrol.GeneralFolderUID + } + // Wildcard grant, no further checks needed if scopeMap["*"] { return true, nil @@ -568,7 +573,7 @@ func (s *Service) checkPermission(ctx context.Context, scopeMap map[string]bool, return false, status.Error(codes.NotFound, "unsupported resource") } - if scopeMap[t.scope(req.Name)] { + if req.Name != "" && scopeMap[t.scope(req.Name)] { return true, nil } diff --git a/pkg/services/authz/rbac/service_test.go b/pkg/services/authz/rbac/service_test.go index 660669e8ba0..62c06f0e910 100644 --- a/pkg/services/authz/rbac/service_test.go +++ b/pkg/services/authz/rbac/service_test.go @@ -17,6 +17,7 @@ import ( "github.com/grafana/authlib/cache" "github.com/grafana/authlib/types" + "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/registry/apis/iam/legacy" @@ -127,19 +128,39 @@ func TestService_checkPermission(t *testing.T) { expected: true, }, { - name: "should return true if no resource is specified", + name: "should check general folder scope for root level resource creation", permissions: []accesscontrol.Permission{ { - Action: "folders:create", + Action: "dashboards:create", + Scope: "folders:uid:general", + Kind: "folders", + Attribute: "uid", + Identifier: "general", }, }, check: CheckRequest{ - Action: "folders:create", - Group: "folder.grafana.app", - Resource: "folders", + Action: "dashboards:create", + Group: "dashboard.grafana.app", + Resource: "dashboards", + Verb: utils.VerbCreate, }, expected: true, }, + { + name: "should fail if user doesn't have general folder scope for root level resource creation", + permissions: []accesscontrol.Permission{ + { + Action: "dashboards:create", + }, + }, + check: CheckRequest{ + Action: "dashboards:create", + Group: "dashboard.grafana.app", + Resource: "dashboards", + Verb: utils.VerbCreate, + }, + expected: false, + }, { name: "should return false if user has no permissions on resource", permissions: []accesscontrol.Permission{}, @@ -174,6 +195,72 @@ func TestService_checkPermission(t *testing.T) { }, expected: true, }, + { + name: "should allow creating a nested resource", + permissions: []accesscontrol.Permission{ + { + Action: "dashboards:create", + Scope: "folders:uid:parent", + Kind: "folders", + Attribute: "uid", + Identifier: "parent", + }, + }, + folders: []store.Folder{{UID: "parent"}}, + check: CheckRequest{ + Action: "dashboards:create", + Group: "dashboard.grafana.app", + Resource: "dashboards", + Name: "", + ParentFolder: "parent", + Verb: utils.VerbCreate, + }, + expected: true, + }, + { + name: "should deny creating a nested resource", + permissions: []accesscontrol.Permission{ + { + Action: "dashboards:create", + Scope: "folders:uid:parent", + Kind: "folders", + Attribute: "uid", + Identifier: "parent", + }, + }, + folders: []store.Folder{{UID: "parent"}}, + check: CheckRequest{ + Action: "dashboards:create", + Group: "dashboard.grafana.app", + Resource: "dashboards", + Name: "", + ParentFolder: "other_parent", + Verb: utils.VerbCreate, + }, + expected: false, + }, + { + name: "should allow if it's an any check", + permissions: []accesscontrol.Permission{ + { + Action: "dashboards:read", + Scope: "folders:uid:parent", + Kind: "folders", + Attribute: "uid", + Identifier: "parent", + }, + }, + folders: []store.Folder{{UID: "parent"}}, + check: CheckRequest{ + Action: "dashboards:read", + Group: "dashboard.grafana.app", + Resource: "dashboards", + Name: "", + ParentFolder: "", + Verb: utils.VerbList, + }, + expected: true, + }, } for _, tc := range testCases { diff --git a/pkg/tests/apis/dashboard/integration/api_validation_test.go b/pkg/tests/apis/dashboard/integration/api_validation_test.go index 573242c998a..6b28d6b2a39 100644 --- a/pkg/tests/apis/dashboard/integration/api_validation_test.go +++ b/pkg/tests/apis/dashboard/integration/api_validation_test.go @@ -2,8 +2,10 @@ package integration import ( "context" + "encoding/json" "fmt" "net/http" + "strconv" "strings" "testing" @@ -14,10 +16,12 @@ import ( "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" + "github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tests/apis" "github.com/grafana/grafana/pkg/tests/testinfra" "github.com/grafana/grafana/pkg/tests/testsuite" + "github.com/grafana/grafana/pkg/util" "github.com/stretchr/testify/require" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -41,8 +45,11 @@ type TestContext struct { EditorUser apis.User ViewerUser apis.User TestFolder *folder.Folder + AdminServiceAccount serviceaccounts.ServiceAccountDTO AdminServiceAccountToken string + EditorServiceAccount serviceaccounts.ServiceAccountDTO EditorServiceAccountToken string + ViewerServiceAccount serviceaccounts.ServiceAccountDTO ViewerServiceAccountToken string OrgID int64 } @@ -54,7 +61,7 @@ func TestIntegrationValidation(t *testing.T) { } // TODO: Skip mode3 - borken due to race conditions while setting default permissions across storage backends - dualWriterModes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2, rest.Mode4, rest.Mode5} + dualWriterModes := []rest.DualWriterMode{rest.Mode0} for _, dualWriterMode := range dualWriterModes { t.Run(fmt.Sprintf("DualWriterMode %d", dualWriterMode), func(t *testing.T) { // Create a K8sTestHelper which will set up a real API server @@ -82,6 +89,7 @@ func testIntegrationValidationForServer(t *testing.T, helper *apis.K8sTestHelper // Create test contexts organization org1Ctx := createTestContext(t, helper, helper.Org1, dualWriterMode) + org2Ctx := createTestContext(t, helper, helper.OrgB, dualWriterMode) t.Run("Organization 1 tests", func(t *testing.T) { t.Run("Dashboard validation tests", func(t *testing.T) { @@ -91,6 +99,27 @@ func testIntegrationValidationForServer(t *testing.T, helper *apis.K8sTestHelper t.Run("Dashboard quota tests", func(t *testing.T) { runQuotaTests(t, org1Ctx) }) + + t.Run("Authorization tests for all identity types", func(t *testing.T) { + runAuthorizationTests(t, org1Ctx) + }) + + t.Run("Dashboard permission tests", func(t *testing.T) { + runDashboardPermissionTests(t, org1Ctx) + }) + + t.Run("Dashboard LIST API test", func(t *testing.T) { + t.Skip("Skip LIST") + runDashboardListTest(t, org1Ctx) + }) + }) + + t.Run("Dashboard HTTP API test", func(t *testing.T) { + runDashboardHttpTest(t, org1Ctx, org2Ctx) + }) + + t.Run("Cross-organization tests", func(t *testing.T) { + runCrossOrgTests(t, org1Ctx, org2Ctx) }) } @@ -733,7 +762,7 @@ func runQuotaTests(t *testing.T, ctx TestContext) { // Helper function to create test context for an organization func createTestContext(t *testing.T, helper *apis.K8sTestHelper, orgUsers apis.OrgUsers, dualWriterMode rest.DualWriterMode) TestContext { // Create test folder - folderTitle := "Test Folder " + orgUsers.Admin.Identity.GetLogin() + folderTitle := "Test Folder Org " + strconv.FormatInt(orgUsers.Admin.Identity.GetOrgID(), 10) testFolder, err := createFolder(t, helper, orgUsers.Admin, folderTitle) require.NoError(t, err, "Failed to create test folder") @@ -745,8 +774,11 @@ func createTestContext(t *testing.T, helper *apis.K8sTestHelper, orgUsers apis.O EditorUser: orgUsers.Editor, ViewerUser: orgUsers.Viewer, TestFolder: testFolder, + AdminServiceAccount: orgUsers.AdminServiceAccount, AdminServiceAccountToken: orgUsers.AdminServiceAccountToken, + EditorServiceAccount: orgUsers.EditorServiceAccount, EditorServiceAccountToken: orgUsers.EditorServiceAccountToken, + ViewerServiceAccount: orgUsers.ViewerServiceAccount, ViewerServiceAccountToken: orgUsers.ViewerServiceAccountToken, OrgID: orgUsers.Admin.Identity.GetOrgID(), } @@ -774,7 +806,6 @@ func getResourceClient(t *testing.T, helper *apis.K8sTestHelper, user apis.User, } // Get a resource client for the specified service token -// nolint:unused func getServiceAccountResourceClient(t *testing.T, helper *apis.K8sTestHelper, token string, orgID int64, gvr schema.GroupVersionResource) *apis.K8sResourceClient { t.Helper() @@ -934,6 +965,7 @@ func createDashboard(t *testing.T, client *apis.K8sResourceClient, title string, databaseDash, err := client.Resource.Get(context.Background(), createdDash.GetName(), v1.GetOptions{}) if err != nil { t.Errorf("Potential caching issue: Unable to retrieve newly created dashboard: %v", err) + return nil, err } createdMeta, _ := utils.MetaAccessor(createdDash) @@ -971,3 +1003,1385 @@ func updateDashboard(t *testing.T, client *apis.K8sResourceClient, dashboard *un // Update the dashboard return client.Resource.Update(context.Background(), dashboard, v1.UpdateOptions{}) } + +// Run unified tests for different identity types (users and service tokens) +func runAuthorizationTests(t *testing.T, ctx TestContext) { + t.Helper() + + // Get clients for each identity type and role + adminUserClient := getResourceClient(t, ctx.Helper, ctx.AdminUser, getDashboardGVR()) + editorUserClient := getResourceClient(t, ctx.Helper, ctx.EditorUser, getDashboardGVR()) + viewerUserClient := getResourceClient(t, ctx.Helper, ctx.ViewerUser, getDashboardGVR()) + + adminTokenClient := getServiceAccountResourceClient(t, ctx.Helper, ctx.AdminServiceAccountToken, ctx.OrgID, getDashboardGVR()) + editorTokenClient := getServiceAccountResourceClient(t, ctx.Helper, ctx.EditorServiceAccountToken, ctx.OrgID, getDashboardGVR()) + viewerTokenClient := getServiceAccountResourceClient(t, ctx.Helper, ctx.ViewerServiceAccountToken, ctx.OrgID, getDashboardGVR()) + + // Define all identities to test + identities := []Identity{ + // User identities + {Name: "Admin user", DashboardClient: adminUserClient, Type: "user"}, + {Name: "Editor user", DashboardClient: editorUserClient, Type: "user"}, + {Name: "Viewer user", DashboardClient: viewerUserClient, Type: "user"}, + + // Token identities + {Name: "Admin token", DashboardClient: adminTokenClient, Type: "token"}, + {Name: "Editor token", DashboardClient: editorTokenClient, Type: "token"}, + {Name: "Viewer token", DashboardClient: viewerTokenClient, Type: "token"}, + } + + // Get admin clients for cleanup based on identity type + adminCleanupClients := map[string]*apis.K8sResourceClient{ + "user": adminUserClient, + "token": adminTokenClient, + } + + // Define test cases for different roles + type roleTest struct { + roleName string + canCreate bool + canUpdate bool + canDelete bool + } + + roleTests := []roleTest{ + { + roleName: "Admin", + canCreate: true, + canUpdate: true, + canDelete: true, + }, + { + roleName: "Editor", + canCreate: true, + canUpdate: true, + canDelete: true, + }, + { + roleName: "Viewer", + canCreate: false, + canUpdate: false, + canDelete: false, + }, + } + + // Create a map of identity client to role capabilities + authTests := make(map[*apis.K8sResourceClient]roleTest) + for _, identity := range identities { + for _, role := range roleTests { + if identity.Name == role.roleName+" "+identity.Type { + authTests[identity.DashboardClient] = role + break + } + } + } + + // Run tests for each identity type + for _, identity := range identities { + identity := identity // Capture range variable + t.Run(identity.Name, func(t *testing.T) { + // Get admin client for cleanup based on identity type + adminClient := adminCleanupClients[identity.Type] + + // Get role capabilities for this identity + roleCapabilities := authTests[identity.DashboardClient] + + // Test dashboard creation (both at root and in folder) + t.Run("dashboard creation", func(t *testing.T) { + // Test locations for dashboard creation + locations := []struct { + name string + folderUID string + }{ + {name: "at root", folderUID: ""}, + {name: "in folder", folderUID: ctx.TestFolder.UID}, + } + + for _, loc := range locations { + t.Run(loc.name, func(t *testing.T) { + if roleCapabilities.canCreate { + // Test can create dashboard + dash, err := createDashboard(t, identity.DashboardClient, identity.Name+" Dashboard "+loc.name, &loc.folderUID, nil) + require.NoError(t, err) + require.NotNil(t, dash) + + // Verify if dashboard was created in the correct folder + if loc.folderUID != "" { + meta, _ := utils.MetaAccessor(dash) + folderUID := meta.GetFolder() + require.Equal(t, loc.folderUID, folderUID, "Dashboard should be in the expected folder") + } + + // Clean up + err = adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + require.NoError(t, err) + } else { + // Test cannot create dashboard + _, err := createDashboard(t, identity.DashboardClient, identity.Name+" Dashboard "+loc.name, nil, nil) + require.Error(t, err) + } + }) + } + }) + + // Test dashboard updates + t.Run("dashboard update", func(t *testing.T) { + // Create a dashboard with admin + dash, err := createDashboard(t, adminClient, "Dashboard to Update by "+identity.Name, nil, nil) + require.NoError(t, err) + require.NotNil(t, dash) + + if roleCapabilities.canUpdate { + // Test can update dashboard + updatedDash, err := updateDashboard(t, identity.DashboardClient, dash, "Updated by "+identity.Name, nil) + require.NoError(t, err) + require.NotNil(t, updatedDash) + + // Verify the update + meta, _ := utils.MetaAccessor(updatedDash) + require.Equal(t, "Updated by "+identity.Name, meta.FindTitle("")) + } else { + // Test cannot update dashboard + _, err := updateDashboard(t, identity.DashboardClient, dash, "Updated by "+identity.Name, nil) + require.Error(t, err) + } + + // Clean up + err = adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + require.NoError(t, err) + }) + + // Test dashboard deletion permissions + t.Run("dashboard deletion", func(t *testing.T) { + // Create a dashboard with admin + dash, err := createDashboard(t, adminClient, "Dashboard for deletion test by "+identity.Name, nil, nil) + require.NoError(t, err) + require.NotNil(t, dash) + + // Attempt to delete + err = identity.DashboardClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + if roleCapabilities.canDelete { + require.NoError(t, err, "Should be able to delete dashboard") + } else { + require.Error(t, err, "Should not be able to delete dashboard") + // Clean up with admin if the test identity couldn't delete + err = adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + require.NoError(t, err) + } + }) + + // TODO: Check if vieweing permission can be revoked as well. + // Test dashboard viewing for all roles + t.Run("dashboard viewing", func(t *testing.T) { + // Create a dashboard with admin + dash, err := createDashboard(t, adminClient, "Dashboard for "+identity.Name+" to view", nil, nil) + require.NoError(t, err) + require.NotNil(t, dash) + + // Get the dashboard with the test identity + viewedDash, err := identity.DashboardClient.Resource.Get(context.Background(), dash.GetName(), v1.GetOptions{}) + require.NoError(t, err, "All identities should be able to view dashboards") + require.NotNil(t, viewedDash) + + // Clean up + err = adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + require.NoError(t, err) + }) + }) + } +} + +// Run tests for dashboard permissions +func runDashboardPermissionTests(t *testing.T, ctx TestContext) { + t.Helper() + + // Get clients for each user + adminClient := getResourceClient(t, ctx.Helper, ctx.AdminUser, getDashboardGVR()) + editorClient := getResourceClient(t, ctx.Helper, ctx.EditorUser, getDashboardGVR()) + viewerClient := getResourceClient(t, ctx.Helper, ctx.ViewerUser, getDashboardGVR()) + + // Get folder clients + adminFolderClient := getResourceClient(t, ctx.Helper, ctx.AdminUser, getFolderGVR()) + + // Test custom dashboard permissions + t.Run("Dashboard with custom permissions", func(t *testing.T) { + // Create a dashboard with admin + dash, err := createDashboard(t, adminClient, "Dashboard with Custom Permissions", nil, nil) + require.NoError(t, err) + require.NotNil(t, dash) + + // Get the dashboard ID + dashUID := dash.GetName() + + // Set permissions for the viewer to edit using HTTP API + setResourceUserPermission(t, ctx, ctx.AdminUser, true, dashUID, addUserPermission(t, nil, ctx.ViewerUser, ResourcePermissionLevelEdit)) + + // Now the viewer should be able to update the dashboard + viewedDash, err := viewerClient.Resource.Get(context.Background(), dashUID, v1.GetOptions{}) + require.NoError(t, err) + + // Update the dashboard with viewer (should succeed because of custom permissions) + updatedDash, err := updateDashboard(t, viewerClient, viewedDash, "Updated by Viewer with Permission", nil) + require.NoError(t, err) + require.NotNil(t, updatedDash) + + // Verify the update + meta, _ := utils.MetaAccessor(updatedDash) + require.Equal(t, "Updated by Viewer with Permission", meta.FindTitle("")) + + // Clean up + err = adminClient.Resource.Delete(context.Background(), dashUID, v1.DeleteOptions{}) + require.NoError(t, err) + }) + + // Test dashboard-specific permission overrides (new test case) + t.Run("Dashboard-specific permission overrides", func(t *testing.T) { + // Create multiple dashboards with admin + dash1, err := createDashboard(t, adminClient, "Dashboard with No Custom Permissions", nil, nil) + require.NoError(t, err) + require.NotNil(t, dash1) + dash1UID := dash1.GetName() + + dash2, err := createDashboard(t, adminClient, "Dashboard with Viewer Edit Permission", nil, nil) + require.NoError(t, err) + require.NotNil(t, dash2) + dash2UID := dash2.GetName() + + // Set EDIT permissions for the viewer on dash2 only + setResourceUserPermission(t, ctx, ctx.AdminUser, true, dash2UID, addUserPermission(t, nil, ctx.ViewerUser, ResourcePermissionLevelEdit)) + + // Verify viewer cannot edit dashboard1 (no custom permissions) + _, err = updateDashboard(t, viewerClient, dash1, "This should fail - no permissions", nil) + require.Error(t, err, "Viewer should not be able to update dashboard without permissions") + + // Verify viewer can edit dashboard2 (with custom permissions) + viewedDash2, err := viewerClient.Resource.Get(context.Background(), dash2UID, v1.GetOptions{}) + require.NoError(t, err) + + updatedDash2, err := updateDashboard(t, viewerClient, viewedDash2, "Updated by Viewer with Dashboard-Specific Permission", nil) + require.NoError(t, err) + require.NotNil(t, updatedDash2) + + // Verify the update + meta, _ := utils.MetaAccessor(updatedDash2) + require.Equal(t, "Updated by Viewer with Dashboard-Specific Permission", meta.FindTitle("")) + + // Also check viewer can delete the dashboard they have EDIT permission on + err = viewerClient.Resource.Delete(context.Background(), dash2UID, v1.DeleteOptions{}) + require.NoError(t, err, "Viewer should be able to delete dashboard with EDIT permission") + + // Clean up the other dashboard + err = adminClient.Resource.Delete(context.Background(), dash1UID, v1.DeleteOptions{}) + require.NoError(t, err) + }) + + // Test folder permissions inheritance + t.Run("Dashboard in folder with custom permissions", func(t *testing.T) { + // Create a new folder with the admin + customFolder, err := createFolder(t, ctx.Helper, ctx.AdminUser, "Custom Permission Folder") + require.NoError(t, err, "Failed to create custom permission folder") + folderUID := customFolder.UID + + // Set permissions for the folder - give viewer edit access using HTTP API + setResourceUserPermission(t, ctx, ctx.AdminUser, false, folderUID, addUserPermission(t, nil, ctx.ViewerUser, ResourcePermissionLevelEdit)) + + // Create a dashboard in the folder with admin + dash, err := createDashboard(t, adminClient, "Dashboard in Custom Permission Folder", &folderUID, nil) + require.NoError(t, err) + require.NotNil(t, dash) + + // Get the dashboard with viewer + viewedDash, err := viewerClient.Resource.Get(context.Background(), dash.GetName(), v1.GetOptions{}) + require.NoError(t, err) + require.NotNil(t, viewedDash) + + // Update the dashboard with viewer (should succeed because of folder permissions) + updatedDash, err := updateDashboard(t, viewerClient, viewedDash, "Updated by Viewer with Folder Permission", nil) + require.NoError(t, err) + require.NotNil(t, updatedDash) + + // Verify the update + meta, _ := utils.MetaAccessor(updatedDash) + require.Equal(t, "Updated by Viewer with Folder Permission", meta.FindTitle("")) + + // User should be able to create a dashboard in the folder + dashViewer, err := createDashboard(t, viewerClient, "Dashboard created by Viewer in Custom Permission Folder", &folderUID, nil) + require.NoError(t, err) + require.NotNil(t, dashViewer) + + // Revert granted permissions + setResourceUserPermission(t, ctx, ctx.AdminUser, false, folderUID, generateDefaultResourcePermissions(t)) + + // Clean up dashboard + err = adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + require.NoError(t, err) + err = viewerClient.Resource.Delete(context.Background(), dashViewer.GetName(), v1.DeleteOptions{}) + require.NoError(t, err) + + // Clean up the folder + err = adminFolderClient.Resource.Delete(context.Background(), folderUID, v1.DeleteOptions{}) + require.NoError(t, err) + }) + + // Test moving dashboard to folder without permission + t.Run("Cannot move dashboard to folder without permission", func(t *testing.T) { + // Create two folders with the admin + folder1, err := createFolder(t, ctx.Helper, ctx.AdminUser, "Default Permission Folder") + require.NoError(t, err, "Failed to create default permission folder") + folder1UID := folder1.UID + + folder2, err := createFolder(t, ctx.Helper, ctx.AdminUser, "Viewer Edit Permission Folder") + require.NoError(t, err, "Failed to create folder with viewer edit permissions") + folder2UID := folder2.UID + + // Set permissions for folder2 - give viewer edit access + setResourceUserPermission(t, ctx, ctx.AdminUser, false, folder2UID, addUserPermission(t, nil, ctx.ViewerUser, ResourcePermissionLevelEdit)) + + // Have the viewer create a dashboard in folder2 + viewerDash, err := createDashboard(t, viewerClient, "Dashboard created by Viewer in Edit Permission Folder", &folder2UID, nil) + require.NoError(t, err, "Viewer should be able to create dashboard in folder with edit permissions") + require.NotNil(t, viewerDash) + dashUID := viewerDash.GetName() + + // Verify the dashboard has folder2UID set + meta, _ := utils.MetaAccessor(viewerDash) + folderUID := meta.GetFolder() + require.Equal(t, folder2UID, folderUID, "Dashboard should be in folder2") + + // Try to update the dashboard to move it to folder1 (where viewer has no edit permission) + meta.SetFolder(folder1UID) + + // This update should fail because viewer doesn't have edit permission in folder1 + _, err = viewerClient.Resource.Update(context.Background(), viewerDash, v1.UpdateOptions{}) + require.Error(t, err, "Viewer should not be able to move dashboard to folder without edit permission") + + // We're piggybacking onto this test to test if moving to a non existent folder also fails: + meta.SetFolder("non-existent-folder-uid") + _, err = viewerClient.Resource.Update(context.Background(), viewerDash, v1.UpdateOptions{}) + require.Error(t, err, "Viewer should not be able to move dashboard to non-existent folder") + _, err = adminClient.Resource.Update(context.Background(), viewerDash, v1.UpdateOptions{}) + require.Error(t, err, "Admin should not be able to move dashboard to non-existent folder") + + err = adminClient.Resource.Delete(context.Background(), dashUID, v1.DeleteOptions{}) + require.NoError(t, err, "Failed to delete dashboard") + err = adminFolderClient.Resource.Delete(context.Background(), folder1UID, v1.DeleteOptions{}) + require.NoError(t, err, "Failed to delete folder1") + err = adminFolderClient.Resource.Delete(context.Background(), folder2UID, v1.DeleteOptions{}) + require.NoError(t, err, "Failed to delete folder2") + }) + + // Test creator permissions (new test case) + t.Run("Creator of dashboard gets admin permission", func(t *testing.T) { + // Create a dashboard as an editor user (not admin) + editorCreatedDash, err := createDashboard(t, editorClient, "Dashboard Created by Editor", nil, nil) + require.NoError(t, err) + require.NotNil(t, editorCreatedDash) + dashUID := editorCreatedDash.GetName() + + // Editor should be able to change permissions on their own dashboard (they get Admin permission as creator) + // Give viewer edit access to the dashboard + // Use the editor to set permissions (should succeed because creator has Admin permission) + setResourceUserPermission(t, ctx, ctx.EditorUser, true, dashUID, addUserPermission(t, nil, ctx.ViewerUser, ResourcePermissionLevelEdit)) + + // Now verify the viewer can edit the dashboard + viewedDash, err := viewerClient.Resource.Get(context.Background(), dashUID, v1.GetOptions{}) + require.NoError(t, err) + + updatedDash, err := updateDashboard(t, viewerClient, viewedDash, "Updated by Viewer with Permission from Editor", nil) + require.NoError(t, err) + require.NotNil(t, updatedDash) + + // Verify the update + meta, _ := utils.MetaAccessor(updatedDash) + require.Equal(t, "Updated by Viewer with Permission from Editor", meta.FindTitle("")) + + // Clean up + err = editorClient.Resource.Delete(context.Background(), dashUID, v1.DeleteOptions{}) + require.NoError(t, err, "Editor should be able to delete dashboard they created") + }) + + // Test scenario where admin restricts editor's access to dashboard they created + t.Run("Admin can override creator permissions", func(t *testing.T) { + t.Skip("Have to double check if that's actually the case") + // Create a dashboard as an editor user (not admin) + editorCreatedDash, err := createDashboard(t, editorClient, "Dashboard Created by Editor for Permission Test", nil, nil) + require.NoError(t, err) + require.NotNil(t, editorCreatedDash) + dashUID := editorCreatedDash.GetName() + + // Verify editor can initially edit their dashboard (they have Admin permission as creator) + initialViewedDash, err := editorClient.Resource.Get(context.Background(), dashUID, v1.GetOptions{}) + require.NoError(t, err) + + initialUpdatedDash, err := updateDashboard(t, editorClient, initialViewedDash, "Initial Update by Creator", nil) + require.NoError(t, err) + require.NotNil(t, initialUpdatedDash) + + // Admin restricts editor to view-only on their own dashboard + setResourceUserPermission(t, ctx, ctx.AdminUser, true, dashUID, addUserPermission(t, nil, ctx.EditorUser, ResourcePermissionLevelView)) + + // Now editor should NOT be able to edit the dashboard (admin override should succeed) + viewedDash, err := editorClient.Resource.Get(context.Background(), dashUID, v1.GetOptions{}) + require.NoError(t, err) + + // Update attempt should fail + _, err = updateDashboard(t, editorClient, viewedDash, "This update should fail", nil) + require.Error(t, err, "Editor should not be able to update dashboard after admin restricts permissions") + + // Editor should also not be able to delete the dashboard + err = editorClient.Resource.Delete(context.Background(), dashUID, v1.DeleteOptions{}) + require.Error(t, err, "Editor should not be able to delete dashboard after admin restricts permissions") + + // Admin should be able to delete it + err = adminClient.Resource.Delete(context.Background(), dashUID, v1.DeleteOptions{}) + require.NoError(t, err, "Admin should always be able to delete dashboards") + }) + + // Test cross-org permissions + t.Run("Custom permissions don't extend across organizations", func(t *testing.T) { + // Get client for other org + otherOrgClient := getResourceClient(t, ctx.Helper, ctx.Helper.OrgB.Viewer, getDashboardGVR()) + + // Create a dashboard with admin in the current org + dash, err := createDashboard(t, adminClient, "Dashboard for Cross-Org Permissions Test", nil, nil) + require.NoError(t, err) + require.NotNil(t, dash) + org1DashUID := dash.GetName() + + // Set the highest permissions for the viewer in the current org + setResourceUserPermission(t, ctx, ctx.AdminUser, true, org1DashUID, addUserPermission(t, nil, ctx.ViewerUser, ResourcePermissionLevelAdmin)) + + // Verify the viewer in the current org can now view and update the dashboard + viewerDash, err := viewerClient.Resource.Get(context.Background(), org1DashUID, v1.GetOptions{}) + require.NoError(t, err, "Viewer with custom permissions should be able to view the dashboard") + + _, err = updateDashboard(t, viewerClient, viewerDash, "Updated by Viewer with Admin Permissions", nil) + require.NoError(t, err, "Viewer with admin permissions should be able to update the dashboard") + + // Try to access the dashboard from a viewer in the other org + _, err = otherOrgClient.Resource.Get(context.Background(), org1DashUID, v1.GetOptions{}) + require.Error(t, err, "User from other org should not be able to view dashboard even with custom permissions") + //statusErr := ctx.Helper.AsStatusError(err) + //require.Equal(t, http.StatusNotFound, int(statusErr.Status().Code), "Should get 404 Not Found") + // TODO: Find out why this throws a 500 instead of a 404 with this message: + // an error on the server (\"Internal Server Error: \\\"/apis/dashboard.grafana.app/v1alpha1/namespaces/org-3/dashboards/test-cs6xk\\\": Dashboard not found\") has prevented the request from succeeding" + + // Clean up + err = adminClient.Resource.Delete(context.Background(), org1DashUID, v1.DeleteOptions{}) + require.NoError(t, err) + }) +} + +// Run tests specifically checking cross-org behavior +func runCrossOrgTests(t *testing.T, org1Ctx, org2Ctx TestContext) { + // Get clients for both organizations + org1SuperAdminClient := getResourceClient(t, org1Ctx.Helper, org1Ctx.AdminUser, getDashboardGVR()) + org1FolderClient := getResourceClient(t, org1Ctx.Helper, org1Ctx.AdminUser, getFolderGVR()) + + org2SuperAdminClient := getResourceClient(t, org2Ctx.Helper, org2Ctx.AdminUser, getDashboardGVR()) + org2FolderClient := getResourceClient(t, org2Ctx.Helper, org2Ctx.AdminUser, getFolderGVR()) + + // Org 1 users trying to access org2 + org1CrossEditorClient := org2Ctx.Helper.GetResourceClient(apis.ResourceClientArgs{ + User: org1Ctx.EditorUser, + Namespace: org2Ctx.Helper.Namespacer(org2Ctx.OrgID), + GVR: getDashboardGVR(), + }) + org1CrossViewerClient := org2Ctx.Helper.GetResourceClient(apis.ResourceClientArgs{ + User: org1Ctx.ViewerUser, + Namespace: org2Ctx.Helper.Namespacer(org2Ctx.OrgID), + GVR: getDashboardGVR(), + }) + org1CrossEditorTokenClient := org2Ctx.Helper.GetResourceClient(apis.ResourceClientArgs{ + ServiceAccountToken: org1Ctx.EditorServiceAccountToken, + Namespace: org2Ctx.Helper.Namespacer(org2Ctx.OrgID), + GVR: getDashboardGVR(), + }) + org1CrossViewerTokenClient := org2Ctx.Helper.GetResourceClient(apis.ResourceClientArgs{ + ServiceAccountToken: org1Ctx.ViewerServiceAccountToken, + Namespace: org2Ctx.Helper.Namespacer(org2Ctx.OrgID), + GVR: getDashboardGVR(), + }) + + // Org 2 users trying to access org1 + org2CrossEditorClient := org1Ctx.Helper.GetResourceClient(apis.ResourceClientArgs{ + User: org2Ctx.EditorUser, + Namespace: org1Ctx.Helper.Namespacer(org1Ctx.OrgID), + GVR: getDashboardGVR(), + }) + org2CrossViewerClient := org1Ctx.Helper.GetResourceClient(apis.ResourceClientArgs{ + User: org2Ctx.ViewerUser, + Namespace: org1Ctx.Helper.Namespacer(org1Ctx.OrgID), + GVR: getDashboardGVR(), + }) + org2CrossEditorTokenClient := org1Ctx.Helper.GetResourceClient(apis.ResourceClientArgs{ + ServiceAccountToken: org2Ctx.EditorServiceAccountToken, + Namespace: org1Ctx.Helper.Namespacer(org1Ctx.OrgID), + GVR: getDashboardGVR(), + }) + org2CrossViewerTokenClient := org1Ctx.Helper.GetResourceClient(apis.ResourceClientArgs{ + ServiceAccountToken: org2Ctx.ViewerServiceAccountToken, + Namespace: org1Ctx.Helper.Namespacer(org1Ctx.OrgID), + GVR: getDashboardGVR(), + }) + + // Test dashboard and folder name/UID uniqueness across orgs + t.Run("Dashboard and folder names/UIDs are unique per organization", func(t *testing.T) { + // Create dashboard with same UID in both orgs - should succeed + uid := "cross-org-dash-uid" + dashTitle := "Cross-Org Dashboard" + + // Create in org1 + dash1, err := createDashboard(t, org1SuperAdminClient, dashTitle, nil, &uid) + require.NoError(t, err, "Failed to create dashboard in org1") + + // Create in org2 with same UID - should succeed (UIDs only need to be unique within an org) + dash2, err := createDashboard(t, org2SuperAdminClient, dashTitle, nil, &uid) + require.NoError(t, err, "Failed to create dashboard with same UID in org2") + + // Verify both dashboards were created + require.Equal(t, uid, dash1.GetName(), "Dashboard UID in org1 should match") + require.Equal(t, uid, dash2.GetName(), "Dashboard UID in org2 should match") + + _, err = updateDashboard(t, org1SuperAdminClient, dash1, "Updated in org1", nil) + require.NoError(t, err, "Failed to update dashboard in org1") + + _, err = updateDashboard(t, org2SuperAdminClient, dash2, "Updated in org2", nil) + require.NoError(t, err, "Failed to update dashboard in org2") + + dash1updated, err := org1SuperAdminClient.Resource.Get(context.Background(), uid, v1.GetOptions{}) + require.NoError(t, err, "Failed to get updated dashboard in org1") + meta1, _ := utils.MetaAccessor(dash1updated) + require.Equal(t, "Updated in org1", meta1.FindTitle(""), "Dashboard title in org1 should be updated") + + dash2updated, err := org2SuperAdminClient.Resource.Get(context.Background(), uid, v1.GetOptions{}) + require.NoError(t, err, "Failed to get updated dashboard in org2") + meta2, _ := utils.MetaAccessor(dash2updated) + require.Equal(t, "Updated in org2", meta2.FindTitle(""), "Dashboard title in org2 should be updated") + + // Clean up + err = org1SuperAdminClient.Resource.Delete(context.Background(), uid, v1.DeleteOptions{}) + require.NoError(t, err, "Failed to delete dashboard in org1") + + err = org2SuperAdminClient.Resource.Delete(context.Background(), uid, v1.DeleteOptions{}) + require.NoError(t, err, "Failed to delete dashboard in org2") + + // Repeat test with folders + folderUID := "cross-org-folder-uid" + folderTitle := "Cross-Org Folder" + + // Create folder objects directly with fixed UIDs + folder1 := createFolderObject(t, folderTitle, org1Ctx.Helper.Namespacer(org1Ctx.OrgID), "") + meta1, err = utils.MetaAccessor(folder1) + require.NoError(t, err) + meta1.SetName(folderUID) + meta1.SetGenerateName("") + + folder2 := createFolderObject(t, folderTitle, org2Ctx.Helper.Namespacer(org2Ctx.OrgID), "") + meta2, err = utils.MetaAccessor(folder2) + require.NoError(t, err) + meta2.SetName(folderUID) + meta2.SetGenerateName("") + + // Create folders in both orgs + createdFolder1, err := org1FolderClient.Resource.Create(context.Background(), folder1, v1.CreateOptions{}) + require.NoError(t, err, "Failed to create folder in org1") + + createdFolder2, err := org2FolderClient.Resource.Create(context.Background(), folder2, v1.CreateOptions{}) + require.NoError(t, err, "Failed to create folder with same UID in org2") + + // Verify both folders were created with the same UID + require.Equal(t, folderUID, createdFolder1.GetName(), "Folder UID in org1 should match") + require.Equal(t, folderUID, createdFolder2.GetName(), "Folder UID in org2 should match") + + // Rename folders + _, err = updateDashboard(t, org1FolderClient, createdFolder1, "Updated folder in org1", nil) + require.NoError(t, err, "Failed to update folder in org1") + + _, err = updateDashboard(t, org2FolderClient, createdFolder2, "Updated folderin org2", nil) + require.NoError(t, err, "Failed to update folder in org2") + + folder1updated, err := org1FolderClient.Resource.Get(context.Background(), folderUID, v1.GetOptions{}) + require.NoError(t, err, "Failed to get updated folder in org1") + meta1, _ = utils.MetaAccessor(folder1updated) + require.Equal(t, "Updated folder in org1", meta1.FindTitle(""), "Folder title in org1 should be updated") + + folder2updated, err := org2FolderClient.Resource.Get(context.Background(), folderUID, v1.GetOptions{}) + require.NoError(t, err, "Failed to get updated folder in org2") + meta2, _ = utils.MetaAccessor(folder2updated) + require.Equal(t, "Updated folderin org2", meta2.FindTitle(""), "Folder title in org2 should be updated") + + // Clean up + err = org1FolderClient.Resource.Delete(context.Background(), folderUID, v1.DeleteOptions{}) + require.NoError(t, err, "Failed to delete folder in org1") + + err = org2FolderClient.Resource.Delete(context.Background(), folderUID, v1.DeleteOptions{}) + require.NoError(t, err, "Failed to delete folder in org2") + }) + + // Test cross-organization access + t.Run("Cross-organization access", func(t *testing.T) { + // Create dashboards in both orgs + org1Dashboard, err := createDashboard(t, org1SuperAdminClient, "Org1 Dashboard", nil, nil) + require.NoError(t, err) + require.NotNil(t, org1Dashboard) + org1DashUID := org1Dashboard.GetName() + + org2Dashboard, err := createDashboard(t, org2SuperAdminClient, "Org2 Dashboard", nil, nil) + require.NoError(t, err) + require.NotNil(t, org2Dashboard) + org2DashUID := org2Dashboard.GetName() + + // Clean up at the end + defer func() { + err = org1SuperAdminClient.Resource.Delete(context.Background(), org1DashUID, v1.DeleteOptions{}) + require.NoError(t, err) + + err = org2SuperAdminClient.Resource.Delete(context.Background(), org2DashUID, v1.DeleteOptions{}) + require.NoError(t, err) + }() + + // Test org1 users trying to access org2 dashboard + testCrossOrgAccess := func(client *apis.K8sResourceClient, adminClient *apis.K8sResourceClient, targetDashUID string, description string) { + t.Run(description, func(t *testing.T) { + // Try to get the dashboard + _, err := client.Resource.Get(context.Background(), targetDashUID, v1.GetOptions{}) + require.Error(t, err, "Should not be able to access dashboard from another org") + //statusErr := org1Ctx.Helper.AsStatusError(err) + // TODO: Find out why this throws a 500 instead of a 404 with this message: + // "an error on the server (\"Internal Server Error: \\\"/apis/dashboard.grafana.app/v1alpha1/namespaces/default/dashboards/test-rbm2q\\\": Dashboard not found\") has prevented the request from succeeding" + //require.Equal(t, http.StatusNotFound, int(statusErr.Status().Code), "Should get 404 Not Found") + + // Get a dashboard as admin from the target org to then send an update request + dash, err := adminClient.Resource.Get(context.Background(), targetDashUID, v1.GetOptions{}) + require.NoError(t, err) + + // Try to update the dashboard + _, err = updateDashboard(t, client, dash, "Renamed dashboard", nil) + require.Error(t, err, "Should not be able to update dashboard from another org") + + // Try to delete the dashboard + err = client.Resource.Delete(context.Background(), targetDashUID, v1.DeleteOptions{}) + require.Error(t, err, "Should not be able to delete dashboard from another org") + + // Verify that the rename and delete were not successful + dash, err = adminClient.Resource.Get(context.Background(), targetDashUID, v1.GetOptions{}) + require.NoError(t, err) + meta, _ := utils.MetaAccessor(dash) + require.NotEqual(t, "Renamed dashboard", meta.FindTitle(""), "Dashboard title should not be changed") + }) + } + + // Test real users from org1 trying to access org2 dashboard + testCrossOrgAccess(org1CrossEditorClient, org2SuperAdminClient, org2DashUID, "Org1 editor cannot access Org2 dashboard") + testCrossOrgAccess(org1CrossViewerClient, org2SuperAdminClient, org2DashUID, "Org1 viewer cannot access Org2 dashboard") + + // Test real users from org2 trying to access org1 dashboard + testCrossOrgAccess(org2CrossEditorClient, org1SuperAdminClient, org1DashUID, "Org2 editor cannot access Org1 dashboard") + testCrossOrgAccess(org2CrossViewerClient, org1SuperAdminClient, org1DashUID, "Org2 viewer cannot access Org1 dashboard") + + // Test service accounts from org1 trying to access org2 dashboard + testCrossOrgAccess(org1CrossEditorTokenClient, org2SuperAdminClient, org2DashUID, "Org1 editor token cannot access Org2 dashboard") + testCrossOrgAccess(org1CrossViewerTokenClient, org2SuperAdminClient, org2DashUID, "Org1 viewer token cannot access Org2 dashboard") + + // Test service accounts from org2 trying to access org1 dashboard + testCrossOrgAccess(org2CrossEditorTokenClient, org1SuperAdminClient, org1DashUID, "Org2 editor token cannot access Org1 dashboard") + testCrossOrgAccess(org2CrossViewerTokenClient, org1SuperAdminClient, org1DashUID, "Org2 viewer token cannot access Org1 dashboard") + }) +} + +type ResourcePermissionSetting struct { + Level ResourcePermissionLevel `json:"permission"` + + // Only set one of these! + Role *ResourcePermissionRole `json:"role,omitempty"` + UserID *int64 `json:"userId,omitempty"` + TeamID *int64 `json:"teamId,omitempty"` +} + +type ResourcePermissionLevel int + +const ( + ResourcePermissionLevelView ResourcePermissionLevel = 1 + ResourcePermissionLevelEdit ResourcePermissionLevel = 2 + ResourcePermissionLevelAdmin ResourcePermissionLevel = 4 +) + +type ResourcePermissionRole string + +const ( + ResourcePermissionRoleViewer ResourcePermissionRole = "Viewer" + ResourcePermissionRoleEditor ResourcePermissionRole = "Editor" +) + +func generateDefaultResourcePermissions(t *testing.T) []ResourcePermissionSetting { + t.Helper() + + viewerRole := ResourcePermissionRoleViewer + editorRole := ResourcePermissionRoleEditor + + return []ResourcePermissionSetting{ + { + Level: ResourcePermissionLevelView, + Role: &viewerRole, + }, + { + Level: ResourcePermissionLevelEdit, + Role: &editorRole, + }, + } +} + +func addUserPermission(t *testing.T, basePermissions *[]ResourcePermissionSetting, targetUser apis.User, level ResourcePermissionLevel) []ResourcePermissionSetting { + t.Helper() + + var permissions []ResourcePermissionSetting + if basePermissions == nil { + permissions = generateDefaultResourcePermissions(t) + } else { + permissions = *basePermissions + } + + userIdInt64, err := identity.UserIdentifier(targetUser.Identity.GetID()) + require.NoError(t, err) + + return append(permissions, ResourcePermissionSetting{ + Level: level, + UserID: &userIdInt64, + }) +} + +// Helper function to set permissions for a user via the HTTP API +func setResourceUserPermission(t *testing.T, ctx TestContext, actingUser apis.User, isDashboard bool, resourceUID string, permissions []ResourcePermissionSetting) { + t.Helper() + + // TODO: Use /apis once available + + type permissionRequest struct { + Items []ResourcePermissionSetting `json:"items"` + } + + reqBody := permissionRequest{ + Items: permissions, + } + + jsonBody, err := json.Marshal(reqBody) + require.NoError(t, err, "Failed to marshal permissions to JSON") + + // TODO: Use /apis once available + var path string + if isDashboard { + path = fmt.Sprintf("/api/dashboards/uid/%s/permissions", resourceUID) + } else { + path = fmt.Sprintf("/api/folders/%s/permissions", resourceUID) + } + + resp := apis.DoRequest(ctx.Helper, apis.RequestParams{ + User: actingUser, + Method: http.MethodPost, + Path: path, + Body: jsonBody, + ContentType: "application/json", + }, &struct{}{}) + + // Check response status code + require.Equal(t, http.StatusOK, resp.Response.StatusCode, "Failed to set permissions for %s", resourceUID) +} + +// Test creating a dashboard via HTTP and deleting it +func runDashboardHttpTest(t *testing.T, ctx TestContext, foreignOrgCtx TestContext) { + t.Helper() + // Define test cases for locations and users + locationTestCases := []struct { + name string + folderUID string + }{ + { + name: "Root dashboard", + folderUID: "", + }, + { + name: "Folder dashboard", + folderUID: ctx.TestFolder.UID, + }, + } + + userTestCases := []struct { + name string + user apis.User + canCreate bool + canUpdate bool + canView bool + }{ + { + name: "Admin", + user: ctx.AdminUser, + canCreate: true, + canUpdate: true, + canView: true, + }, + { + name: "Editor", + user: ctx.EditorUser, + canCreate: true, + canUpdate: true, + canView: true, + }, + { + name: "Viewer", + user: ctx.ViewerUser, + canCreate: false, + canUpdate: false, + canView: true, + }, + { + name: "Foreign Org Admin", + user: foreignOrgCtx.AdminUser, + canCreate: false, + canUpdate: false, + canView: false, + }, + { + name: "Foreign Org Editor", + user: foreignOrgCtx.EditorUser, + canCreate: false, + canUpdate: false, + canView: false, + }, + { + name: "Foreign Org Viewer", + user: foreignOrgCtx.ViewerUser, + canCreate: false, + canUpdate: false, + canView: false, + }, + } + + // Test all combinations + for _, locTC := range locationTestCases { + for _, userTC := range userTestCases { + testName := fmt.Sprintf("%s by %s", locTC.name, userTC.name) + t.Run(testName, func(t *testing.T) { + // Create a unique dashboard UID - ensure it's 40 chars max + dashboardUID := fmt.Sprintf("test-%s-%s-%s", + "POST", + userTC.name[:3], // Use only first 3 chars of user role + util.GenerateShortUID()[:8]) // Use only first 8 chars of UID + dashboardTitle := fmt.Sprintf("Dashboard Created via %s - %s by %s", + "POST", locTC.name, userTC.name) + + // Construct the dashboard URL + dashboardPath := fmt.Sprintf("/apis/dashboard.grafana.app/v1alpha1/namespaces/%s/dashboards", ctx.Helper.Namespacer(ctx.OrgID)) + + // Create dashboard JSON with a single template + var metadata string + if locTC.folderUID != "" { + metadata = fmt.Sprintf(`"name": "%s", "annotations": {"grafana.app/folder": "%s", "grafana.app/grant-permissions": "default"}`, + dashboardUID, locTC.folderUID) + } else { + metadata = fmt.Sprintf(`"name": "%s", "annotations": {"grafana.app/grant-permissions": "default"}`, dashboardUID) + } + + dashboardJSON := fmt.Sprintf(`{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v1alpha1", + "metadata": { + %s + }, + "spec": { + "title": "%s", + "schemaVersion": 41, + "layout": { + "kind": "GridLayout", + "items": [] + } + } + }`, metadata, dashboardTitle) + + // Make the request to create the dashboard + createResp := apis.DoRequest(ctx.Helper, apis.RequestParams{ + User: userTC.user, + Method: http.MethodPost, + Path: dashboardPath, + Body: []byte(dashboardJSON), + ContentType: "application/json", + }, &struct{}{}) + + // Check if the creation was successful or failed as expected + adminClient := getResourceClient(t, ctx.Helper, ctx.AdminUser, getDashboardGVR()) + + if userTC.canCreate { + require.Equal(t, http.StatusCreated, createResp.Response.StatusCode, + "Failed to %s dashboard as %s: %s", "POST", userTC.user.Identity.GetLogin(), createResp.Response.Status) + + // Construct the dashboard path with the actual UID for GET/DELETE + dashboardPath = fmt.Sprintf("/apis/dashboard.grafana.app/v1alpha1/namespaces/%s/dashboards/%s", + ctx.Helper.Namespacer(ctx.OrgID), dashboardUID) + + // Verify the dashboard was created by getting it via the admin client + dash, err := adminClient.Resource.Get(context.Background(), dashboardUID, v1.GetOptions{}) + require.NoError(t, err, "Failed to get dashboard after POST") + + // Verify the dashboard properties + meta, err := utils.MetaAccessor(dash) + require.NoError(t, err) + require.Equal(t, dashboardTitle, meta.FindTitle(""), "Dashboard title does not match") + + // Verify folder reference if applicable + if locTC.folderUID != "" { + require.Equal(t, locTC.folderUID, meta.GetFolder(), "Dashboard folder reference does not match") + } + + // Try to GET the dashboard with the test user + getResp := apis.DoRequest(ctx.Helper, apis.RequestParams{ + User: userTC.user, + Method: http.MethodGet, + Path: dashboardPath, + }, &struct{}{}) + + require.Equal(t, http.StatusOK, getResp.Response.StatusCode, + "User %s should be able to GET dashboard: %s", userTC.name, getResp.Response.Status) + + // Extract the dashboard object from the GET response + var dashObj map[string]interface{} + err = json.Unmarshal(getResp.Body, &dashObj) + require.NoError(t, err, "Failed to unmarshal dashboard JSON from GET") + + // Test both update methods for each user role + for _, updateUser := range userTestCases { + testDashboardHttpUpdateMethods(t, ctx, dashboardPath, dashboardTitle, updateUser.user, updateUser.canUpdate) + } + + // Verify whether every role can GET the dashboard that was created + for _, viewUser := range userTestCases { + roleGetResp := apis.DoRequest(ctx.Helper, apis.RequestParams{ + User: viewUser.user, + Method: http.MethodGet, + Path: dashboardPath, + }, &struct{}{}) + + if viewUser.canView { + require.Equal(t, http.StatusOK, roleGetResp.Response.StatusCode, + "User %s should be able to GET dashboard: %s", viewUser.name, roleGetResp.Response.Status) + } else { + require.NotEqual(t, http.StatusOK, roleGetResp.Response.StatusCode, + "User %s should not be able to GET dashboard: %s", viewUser.name, roleGetResp.Response.Status) + } + } + + // Delete the dashboard with DELETE request + deleteResp := apis.DoRequest(ctx.Helper, apis.RequestParams{ + User: userTC.user, + Method: http.MethodDelete, + Path: dashboardPath, + }, &struct{}{}) + + // Check response status code + require.Equal(t, http.StatusOK, deleteResp.Response.StatusCode, + "Failed to DELETE dashboard: %s", deleteResp.Response.Status) + + // Verify the dashboard was deleted + _, err = adminClient.Resource.Get(context.Background(), dashboardUID, v1.GetOptions{}) + //require.ErrorIs(t, err, dashboards.ErrDashboardNotFound, "Dashboard should be deleted") + require.Error(t, err, "Dashboard should be deleted") + } else { + require.NotEqual(t, http.StatusCreated, createResp.Response.StatusCode, + "%s should not be able to create dashboard via %s", userTC.name, "POST") + + // Always verify the dashboard wasn't created by checking for its UID + // Verify the dashboard was not created + _, err := adminClient.Resource.Get(context.Background(), dashboardUID, v1.GetOptions{}) + //require.ErrorIs(t, err, dashboards.ErrDashboardNotFound, "Dashboard should never have been created") + require.Error(t, err, "Dashboard should never have been created") + } + }) + } + } +} + +// Helper function to retrieve a dashboard via HTTP +func getDashboardViaHTTP(t *testing.T, ctx *TestContext, dashboardPath string, user apis.User) (map[string]interface{}, error) { + getResp := apis.DoRequest(ctx.Helper, apis.RequestParams{ + User: user, + Method: http.MethodGet, + Path: dashboardPath, + }, &struct{}{}) + + if getResp.Response.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to get dashboard: %s", getResp.Response.Status) + } + + var dashObj map[string]interface{} + err := json.Unmarshal(getResp.Body, &dashObj) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal dashboard JSON: %v", err) + } + + return dashObj, nil +} + +// Helper function to test dashboard updates via different http methods +func testDashboardHttpUpdateMethods(t *testing.T, ctx TestContext, dashboardPath string, originalTitle string, + updateUser apis.User, canUpdate bool) { + // Helper to verify update results based on permissions + verifyUpdateResults := func(updateMethod string, updateTitle string, updateResp apis.K8sResponse[struct{}]) { + if canUpdate { + require.Equal(t, http.StatusOK, updateResp.Response.StatusCode, + "Failed to update dashboard with %s as %s: %s", + updateMethod, updateUser.Identity.GetLogin(), updateResp.Response.Status) + + // Verify update by getting fresh dashboard state + updatedDashObj, err := getDashboardViaHTTP(t, &ctx, dashboardPath, ctx.AdminUser) + require.NoError(t, err, "Failed to get dashboard after update") + + // Extract title from the updated dashboard + updatedTitle := updatedDashObj["spec"].(map[string]interface{})["title"].(string) + require.Equal(t, updateTitle, updatedTitle, + "Dashboard title not updated via %s", updateMethod) + } else { + require.NotEqual(t, http.StatusOK, updateResp.Response.StatusCode, + "%s should not be able to update dashboard via %s", + updateUser.Identity.GetLogin(), updateMethod) + } + } + + // Test PUT update + t.Run(fmt.Sprintf("Update via %s by %s", "PUT", updateUser.Identity.GetLogin()), func(t *testing.T) { + updateTitle := fmt.Sprintf("%s - Updated via PUT by %s", originalTitle, updateUser.Identity.GetLogin()) + + // Always get fresh dashboard state via HTTP + // Use admin to ensure we can always retrieve it + freshDashObj, err := getDashboardViaHTTP(t, &ctx, dashboardPath, ctx.AdminUser) + require.NoError(t, err, "Failed to get fresh dashboard for update") + + // Modify title for PUT using fresh dashboard object + specMap := freshDashObj["spec"].(map[string]interface{}) + specMap["title"] = updateTitle + freshDashObj["spec"] = specMap + + // Convert to JSON + updatedJSON, err := json.Marshal(freshDashObj) + require.NoError(t, err, "Failed to marshal dashboard JSON") + + // Make PUT request + updateResp := apis.DoRequest(ctx.Helper, apis.RequestParams{ + User: updateUser, + Method: http.MethodPut, + Path: dashboardPath, + Body: updatedJSON, + ContentType: "application/json", + }, &struct{}{}) + + verifyUpdateResults("PUT", updateTitle, updateResp) + }) + + // Test PATCH update + t.Run(fmt.Sprintf("Update via %s by %s", "PATCH", updateUser.Identity.GetLogin()), func(t *testing.T) { + updateTitle := fmt.Sprintf("%s - Updated via PATCH by %s", originalTitle, updateUser.Identity.GetLogin()) + + // Create a JSON patch document + patchJSON := fmt.Sprintf(`[ + {"op": "replace", "path": "/spec/title", "value": "%s"} + ]`, updateTitle) + + // Make PATCH request + updateResp := apis.DoRequest(ctx.Helper, apis.RequestParams{ + User: updateUser, + Method: http.MethodPatch, + Path: dashboardPath, + Body: []byte(patchJSON), + ContentType: "application/json-patch+json", + }, &struct{}{}) + + verifyUpdateResults("PATCH", updateTitle, updateResp) + }) +} + +// Test dashboard list API with complex permission scenarios +func runDashboardListTest(t *testing.T, ctx TestContext) { + t.Helper() + + // Make sure no dashboards exist before we start + adminClient := getResourceClient(t, ctx.Helper, ctx.AdminUser, getDashboardGVR()) + dashList, err := adminClient.Resource.List(context.Background(), v1.ListOptions{}) + require.NoError(t, err) + if len(dashList.Items) > 0 { + for _, dash := range dashList.Items { + t.Logf("Found dashboard: %s", dash.GetName()) + } + t.Fatalf("Expected no dashboards to exist, but found %d", len(dashList.Items)) + } + require.Equal(t, 0, len(dashList.Items), "Expected no dashboards to exist") + + // Also check that no folders exist + adminFolderClient := getResourceClient(t, ctx.Helper, ctx.AdminUser, getFolderGVR()) + folderList, err := adminFolderClient.Resource.List(context.Background(), v1.ListOptions{}) + require.NoError(t, err) + if len(folderList.Items) != 1 { + for _, folder := range folderList.Items { + t.Logf("Found folder: %s", folder.GetName()) + } + t.Fatalf("Expected 1 folder to exist, but found %d", len(folderList.Items)) + } + + // Define a map of user types to their clients + clients := map[string]struct { + userClient *apis.K8sResourceClient + tokenClient *apis.K8sResourceClient + folderClient *apis.K8sResourceClient + }{ + "Admin": { + userClient: getResourceClient(t, ctx.Helper, ctx.AdminUser, getDashboardGVR()), + folderClient: getResourceClient(t, ctx.Helper, ctx.AdminUser, getFolderGVR()), + tokenClient: getServiceAccountResourceClient(t, ctx.Helper, ctx.AdminServiceAccountToken, ctx.OrgID, getDashboardGVR()), + }, + "Editor": { + userClient: getResourceClient(t, ctx.Helper, ctx.EditorUser, getDashboardGVR()), + folderClient: getResourceClient(t, ctx.Helper, ctx.EditorUser, getFolderGVR()), + tokenClient: getServiceAccountResourceClient(t, ctx.Helper, ctx.EditorServiceAccountToken, ctx.OrgID, getDashboardGVR()), + }, + "Viewer": { + userClient: getResourceClient(t, ctx.Helper, ctx.ViewerUser, getDashboardGVR()), + folderClient: getResourceClient(t, ctx.Helper, ctx.ViewerUser, getFolderGVR()), + tokenClient: getServiceAccountResourceClient(t, ctx.Helper, ctx.ViewerServiceAccountToken, ctx.OrgID, getDashboardGVR()), + }, + } + + // Define identities for testing LIST operation + identities := make([]Identity, 0, len(clients)*2+1) + for role, c := range clients { + identities = append(identities, + Identity{Name: role + " user", DashboardClient: c.userClient, FolderClient: c.folderClient, Type: "user"}, + Identity{Name: role + " token", DashboardClient: c.tokenClient, FolderClient: c.folderClient, Type: "token"}) + } + + // Define permission schemes with role/user access mapping + type accessConfig struct { + admin bool + editor bool + viewer bool + } + + // Create 5 folders with different permission schemes + folderConfigs := []struct { + name string + permissions func(t *testing.T, ctx TestContext, resourceUID string, isDashboard bool) + access accessConfig + }{ + { + name: "Admin only", + permissions: func(t *testing.T, ctx TestContext, resourceUID string, isDashboard bool) { + permissions := []ResourcePermissionSetting{} + setResourceUserPermission(t, ctx, ctx.AdminUser, isDashboard, resourceUID, permissions) + }, + access: accessConfig{admin: true}, + }, + { + name: "Admin and Editor", + permissions: func(t *testing.T, ctx TestContext, resourceUID string, isDashboard bool) { + editorRole := ResourcePermissionRoleEditor + permissions := []ResourcePermissionSetting{ + {Level: ResourcePermissionLevelEdit, Role: &editorRole}, + } + setResourceUserPermission(t, ctx, ctx.AdminUser, isDashboard, resourceUID, permissions) + }, + access: accessConfig{admin: true, editor: true}, + }, + { + name: "Default permissions", + permissions: func(t *testing.T, ctx TestContext, resourceUID string, isDashboard bool) { + // Default permissions - no need to modify + }, + access: accessConfig{admin: true, editor: true, viewer: true}, + }, + { + name: "Viewer user specific", + permissions: func(t *testing.T, ctx TestContext, resourceUID string, isDashboard bool) { + viewerUserId, _ := identity.UserIdentifier(ctx.ViewerUser.Identity.GetID()) + viewerServiceUserId := ctx.ViewerServiceAccount.Id + permissions := []ResourcePermissionSetting{ + {Level: ResourcePermissionLevelEdit, UserID: &viewerUserId}, + {Level: ResourcePermissionLevelEdit, UserID: &viewerServiceUserId}, + } + setResourceUserPermission(t, ctx, ctx.AdminUser, isDashboard, resourceUID, permissions) + }, + access: accessConfig{admin: true, viewer: true}, + }, + { + name: "Editor user specific", + permissions: func(t *testing.T, ctx TestContext, resourceUID string, isDashboard bool) { + editorUserId, _ := identity.UserIdentifier(ctx.EditorUser.Identity.GetID()) + editorServiceUserId := ctx.EditorServiceAccount.Id + permissions := []ResourcePermissionSetting{ + {Level: ResourcePermissionLevelView, UserID: &editorUserId}, + {Level: ResourcePermissionLevelView, UserID: &editorServiceUserId}, + } + setResourceUserPermission(t, ctx, ctx.AdminUser, isDashboard, resourceUID, permissions) + }, + access: accessConfig{admin: true, editor: true}, + }, + } + + // Create dashboards and folders with permissions + rootDashboards := make([]*unstructured.Unstructured, len(folderConfigs)) + folders := make([]*folder.Folder, len(folderConfigs)) + folderDashboards := make([]*unstructured.Unstructured, len(folderConfigs)) + + // Create all test resources (folders, dashboards) in one loop + for i, fc := range folderConfigs { + // Create root dashboard + rootDash, err := createDashboard(t, adminClient, fmt.Sprintf("Root Dashboard - %s", fc.name), nil, nil) + require.NoError(t, err) + rootDashboards[i] = rootDash + fc.permissions(t, ctx, rootDash.GetName(), true) + + // Create folder + folder, err := createFolder(t, ctx.Helper, ctx.AdminUser, fc.name+" folder") + require.NoError(t, err) + folders[i] = folder + fc.permissions(t, ctx, folder.UID, false) + + // Create dashboard in folder + folderDash, err := createDashboard(t, adminClient, fmt.Sprintf("Dashboard in %s folder", fc.name), &folder.UID, nil) + require.NoError(t, err) + folderDashboards[i] = folderDash + } + + folderPermissions := map[string][]string{ + "Admin user": { + "Default permissions folder", + "Editor user specific folder", + "Admin and Editor folder", + "Viewer user specific folder", + "Admin only folder", + "Test Folder Org 1", + }, + "Admin token": { + "Default permissions folder", + "Editor user specific folder", + "Admin and Editor folder", + "Viewer user specific folder", + "Admin only folder", + "Test Folder Org 1", + }, + "Editor user": { + "Default permissions folder", + "Editor user specific folder", + "Admin and Editor folder", + "Test Folder Org 1", + }, + "Editor token": { + "Default permissions folder", + "Editor user specific folder", + "Admin and Editor folder", + "Test Folder Org 1", + }, + "Viewer user": { + "Default permissions folder", + "Viewer user specific folder", + "Test Folder Org 1", + }, + "Viewer token": { + "Default permissions folder", + "Viewer user specific folder", + "Test Folder Org 1", + }, + } + + // Generate expectations based on folderConfigs access rules + expectations := make(map[string][]string) + for _, ident := range identities { + var expectedDashboards []string + + for _, fc := range folderConfigs { + // Check if this identity has access based on its role + hasAccess := false + roleName := strings.Split(ident.Name, " ")[0] // Extract "Admin", "Editor", or "Viewer" + + switch roleName { + case "Admin": + hasAccess = fc.access.admin + case "Editor": + hasAccess = fc.access.editor + case "Viewer": + hasAccess = fc.access.viewer + } + + if hasAccess { + // Add both root dashboard and folder dashboard to expectations + expectedDashboards = append(expectedDashboards, + fmt.Sprintf("Root Dashboard - %s", fc.name), + fmt.Sprintf("Dashboard in %s folder", fc.name)) + } + } + expectations[ident.Name] = expectedDashboards + } + + // Test LIST operation for each identity + for _, identity := range identities { + t.Run(fmt.Sprintf("LIST operation for %s", identity.Name), func(t *testing.T) { + // Get dashboards visible to this identity + clients := []apis.K8sResourceClient{ + *identity.DashboardClient, + *identity.FolderClient, + } + + for _, client := range clients { + t.Run(fmt.Sprintf("LIST operation for %s", client.Args.GVR), func(t *testing.T) { + listOpts := v1.ListOptions{} + dashList, err := client.Resource.List(context.Background(), listOpts) + require.NoError(t, err) + require.NotEmpty(t, dashList.Items) + + // Extract dashboard titles + dashTitles := make([]string, 0, len(dashList.Items)) + for _, dash := range dashList.Items { + meta, err := utils.MetaAccessor(&dash) + require.NoError(t, err) + + dashTitles = append(dashTitles, meta.FindTitle("")) + } + + // Verify expectations + var expectedTitles []string + if client.Args.GVR == getDashboardGVR() { + expectedTitles = expectations[identity.Name] + } else { + expectedTitles = folderPermissions[identity.Name] + } + require.ElementsMatch(t, expectedTitles, dashTitles) + + // Verify all expected items are found + for _, expected := range expectedTitles { + found := false + for _, title := range dashTitles { + if title == expected { + found = true + break + } + } + require.True(t, found, "%s should see dashboard '%s' but didn't", identity.Name, expected) + } + }) + } + }) + } + + // Clean up + t.Run("Cleanup dashboards and folders", func(t *testing.T) { + // Delete all root dashboards + for _, dash := range rootDashboards { + err := adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + require.NoError(t, err) + } + + // Delete all folder dashboards and folders + for i, folder := range folders { + err := adminClient.Resource.Delete(context.Background(), folderDashboards[i].GetName(), v1.DeleteOptions{}) + require.NoError(t, err) + + err = adminFolderClient.Resource.Delete(context.Background(), folder.UID, v1.DeleteOptions{}) + require.NoError(t, err) + } + }) +} diff --git a/scripts/grafana-server/custom.ini b/scripts/grafana-server/custom.ini index f16dfd7706f..68968c502fa 100644 --- a/scripts/grafana-server/custom.ini +++ b/scripts/grafana-server/custom.ini @@ -10,6 +10,9 @@ grafanaAPIServer=true queryLibrary=true queryService=true +[environment] +stack_id = 12345 + [plugins] allow_loading_unsigned_plugins=grafana-extensionstest-app,grafana-extensionexample1-app,grafana-extensionexample2-app,grafana-extensionexample3-app,grafana-e2etest-datasource From ee4d59b5474e629e81f6039ee735ba068c012d16 Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Wed, 23 Apr 2025 00:00:32 -0600 Subject: [PATCH 031/146] Linter: fix conflict (#104370) Linter: fix enterprise conflict --- pkg/tests/apis/dashboard/integration/api_validation_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tests/apis/dashboard/integration/api_validation_test.go b/pkg/tests/apis/dashboard/integration/api_validation_test.go index 6b28d6b2a39..37db48714a1 100644 --- a/pkg/tests/apis/dashboard/integration/api_validation_test.go +++ b/pkg/tests/apis/dashboard/integration/api_validation_test.go @@ -21,7 +21,6 @@ import ( "github.com/grafana/grafana/pkg/tests/apis" "github.com/grafana/grafana/pkg/tests/testinfra" "github.com/grafana/grafana/pkg/tests/testsuite" - "github.com/grafana/grafana/pkg/util" "github.com/stretchr/testify/require" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -31,6 +30,7 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/services/dashboards" // TODO: Check if we can remove this import "github.com/grafana/grafana/pkg/services/quota" + "github.com/grafana/grafana/pkg/util" ) func TestMain(m *testing.M) { From d0d1fb2de22e4d1798f03e9b729b12e826bc0e5e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 07:40:29 +0000 Subject: [PATCH 032/146] Update dependency i18next-browser-languagedetector to v8.0.5 (#104294) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 799a85340f6..263a8945131 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18614,11 +18614,11 @@ __metadata: linkType: hard "i18next-browser-languagedetector@npm:^8.0.0": - version: 8.0.2 - resolution: "i18next-browser-languagedetector@npm:8.0.2" + version: 8.0.5 + resolution: "i18next-browser-languagedetector@npm:8.0.5" dependencies: "@babel/runtime": "npm:^7.23.2" - checksum: 10/2d994fbec7d106da7592d3b22a058f03be59a67e0ef7436bc1b5500dca778dc5eb77df34b4e0a054123d5538ef5f8b5c19cd1d7b862dc3403c4571b48c9cabe8 + checksum: 10/536b7ad6faa2dc7bb64294466372119f02299701f70aa6875c846ed147c478df71c07706dd3e49fb7686186c88af4e66942eb083490405244faba2a959273e4b languageName: node linkType: hard From 1d3d13c0cd0eb0f5c1ef2929cfbdbdab01c6e3ca Mon Sep 17 00:00:00 2001 From: sarah-spang <86264026+sarah-spang@users.noreply.github.com> Date: Wed, 23 Apr 2025 02:41:19 -0600 Subject: [PATCH 033/146] Docs: RBAC - Fix incorrect role name (#104244) Docs: Fix incorrect role name Doc references fixed:licensing:viewer, however, this role does not exist. It should be fixed:licensing:reader --- .../access-control/rbac-fixed-basic-role-definitions/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md b/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md index 193c872d58b..49352d7a600 100644 --- a/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md +++ b/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md @@ -122,7 +122,7 @@ To learn how to use the roles API to determine the role UUIDs, refer to [Manage | `fixed:library.panels:reader` | `fixed_tvTr9CnZ6La5vvUO_U_X1LPnhUs` | `library.panels:read` | Read all library panels. | | `fixed:library.panels:writer` | `fixed_JTljAr21LWLTXCkgfBC4H0lhBC8` | All permissions from `fixed:library.panels:reader` plus
`library.panels:create`
`library.panels:delete`
`library.panels:write` | Create, read, write or delete all library panels and their permissions. | | `fixed:licensing:reader` | `fixed_OADpuXvNEylO2Kelu3GIuBXEAYE` | `licensing:read`
`licensing.reports:read` | Read licensing information and licensing reports. | -| `fixed:licensing:writer` | `fixed_gzbz3rJpQMdaKHt-E4q0PVaKMoE` | All permissions from `fixed:licensing:viewer` and
`licensing:write`
`licensing:delete` | Read licensing information and licensing reports, update and delete the license token. | +| `fixed:licensing:writer` | `fixed_gzbz3rJpQMdaKHt-E4q0PVaKMoE` | All permissions from `fixed:licensing:reader` and
`licensing:write`
`licensing:delete` | Read licensing information and licensing reports, update and delete the license token. | | `fixed:migrationassistant:migrator` | `fixed_LLk2p7TRuBztOAksTQb1Klc8YTk` | `migrationassistant:migrate` | Execute on-prem to cloud migrations through the Migration Assistant. | | `fixed:org.users:reader` | `fixed_oCqNwlVHLOpw7-jAlwp4HzYqwGY` | `org.users:read` | Read users within a single organization. | | `fixed:org.users:writer` | `fixed_VERj5nayasjgf_Yh0sWqqCkxWlw` | All permissions from `fixed:org.users:reader` and
`org.users:add`
`org.users:remove`
`org.users:write` | Within a single organization, add a user, invite a new user, read information about a user and their role, remove a user from that organization, or change the role of a user. | From f932bf7f36bed0c05ace4af6d89b4b540802c8d2 Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Wed, 23 Apr 2025 11:01:40 +0200 Subject: [PATCH 034/146] Jaeger: Run traceID queries through backend when node graph is enabled (#104029) * Refactor current solution * Run traceid queries with enabled node graph through backend * Update test * Fix lint * Uodate based on feedback, don't spread --- .../datasource/jaeger/datasource.test.ts | 98 ++++++++++++++++++- .../plugins/datasource/jaeger/datasource.ts | 50 ++++++---- 2 files changed, 130 insertions(+), 18 deletions(-) diff --git a/public/app/plugins/datasource/jaeger/datasource.test.ts b/public/app/plugins/datasource/jaeger/datasource.test.ts index f3d3c826e2f..2fa37357816 100644 --- a/public/app/plugins/datasource/jaeger/datasource.test.ts +++ b/public/app/plugins/datasource/jaeger/datasource.test.ts @@ -9,7 +9,7 @@ import { PluginType, ScopedVars, } from '@grafana/data'; -import { BackendSrv } from '@grafana/runtime'; +import { BackendSrv, config, DataSourceWithBackend } from '@grafana/runtime'; import { ALL_OPERATIONS_KEY } from './components/SearchForm'; import { JaegerDatasource, JaegerJsonData } from './datasource'; @@ -307,6 +307,102 @@ describe('JaegerDatasource', () => { url: `${defaultSettings.url}/api/traces?service=interpolationText&operation=interpolationText&minDuration=interpolationText&maxDuration=interpolationText&${defaultSearchRangeParams}&lookback=custom`, }); }); + + describe('when jaegerBackendMigration feature toggle is enabled', () => { + let originalFeatureToggleValue: boolean | undefined; + + beforeEach(() => { + originalFeatureToggleValue = config.featureToggles.jaegerBackendMigration; + config.featureToggles.jaegerBackendMigration = true; + }); + + afterEach(() => { + config.featureToggles.jaegerBackendMigration = originalFeatureToggleValue; + }); + + it('should add node graph frames to response when nodeGraph is enabled and query is a trace ID query', async () => { + // Create a datasource with nodeGraph enabled + const settings = { + ...defaultSettings, + jsonData: { + ...defaultSettings.jsonData, + nodeGraph: { enabled: true }, + }, + }; + + const ds = new JaegerDatasource(settings); + + // Mock the super.query method to return our mock response + jest.spyOn(DataSourceWithBackend.prototype, 'query').mockImplementation(() => { + return of({ + data: [ + { + fields: testResponseDataFrameFields, + values: testResponseDataFrameFields.values, + }, + ], + }); + }); + + // Create a query without queryType (trace ID query) + const query = { + ...defaultQuery, + targets: [ + { + query: '12345', + refId: '1', + }, + ], + }; + + // Execute the query + const response = await lastValueFrom(ds.query(query)); + // Verify that the response contains the original data plus node graph frames + expect(response.data.length).toBe(3); + }); + + it('should not add node graph frames when nodeGraph is disabled', async () => { + // Create a datasource with nodeGraph disabled + const settings = { + ...defaultSettings, + jsonData: { + ...defaultSettings.jsonData, + nodeGraph: { enabled: false }, + }, + }; + + const ds = new JaegerDatasource(settings); + + // Mock the super.query method to return our mock response + jest.spyOn(DataSourceWithBackend.prototype, 'query').mockImplementation(() => { + return of({ + data: [ + { + fields: testResponseDataFrameFields, + values: testResponseDataFrameFields.values, + }, + ], + }); + }); + + // Create a query without queryType (trace ID query) + const query = { + ...defaultQuery, + targets: [ + { + query: '12345', + refId: '1', + }, + ], + }; + + // Execute the query + const response = await lastValueFrom(ds.query(query)); + // Verify that the response contains only the original data + expect(response.data.length).toBe(1); + expect(response.data[0].fields).toMatchObject(testResponseDataFrameFields); + }); + }); }); describe('when performing testDataSource', () => { diff --git a/public/app/plugins/datasource/jaeger/datasource.ts b/public/app/plugins/datasource/jaeger/datasource.ts index c0cca5ad098..89296bf9831 100644 --- a/public/app/plugins/datasource/jaeger/datasource.ts +++ b/public/app/plugins/datasource/jaeger/datasource.ts @@ -13,9 +13,10 @@ import { getDefaultTimeRange, MutableDataFrame, ScopedVars, + toDataFrame, urlUtil, } from '@grafana/data'; -import { NodeGraphOptions, SpanBarOptions } from '@grafana/o11y-ds-frontend'; +import { createNodeGraphFrames, NodeGraphOptions, SpanBarOptions } from '@grafana/o11y-ds-frontend'; import { BackendSrvRequest, config, @@ -69,29 +70,29 @@ export class JaegerDatasource extends DataSourceWithBackend): Observable { - // No query type means that the query is a trace ID query - // If all targets are trace ID queries, we can use the backend querying - const allTargetsTraceIdQuery = options.targets.every((target) => !target.queryType); - const allTargetsDependencyGraph = options.targets.every((target) => target.queryType === 'dependencyGraph'); - // We have not migrated the node graph to the backend - // If the node graph is disabled, we can use the backend migration - const nodeGraphDisabled = !this.nodeGraph?.enabled; - if ( - config.featureToggles.jaegerBackendMigration && - (allTargetsTraceIdQuery || allTargetsDependencyGraph) && - nodeGraphDisabled - ) { - return super.query(options); - } - // At this moment we expect only one target. In case we somehow change the UI to be able to show multiple // traces at one we need to change this. const target: JaegerQuery = options.targets[0]; - if (!target) { return of({ data: [emptyTraceDataFrame] }); } + if ( + config.featureToggles.jaegerBackendMigration && + // No query type means that the query is a trace ID query + (!target.queryType || target.queryType === 'dependencyGraph') + ) { + return super.query({ ...options, targets: [target] }).pipe( + map((response) => { + // If the node graph is enabled and the query is a trace ID query, add the node graph frames to the response + if (this.nodeGraph?.enabled && !target.queryType) { + return addNodeGraphFramesToResponse(response); + } + return response; + }) + ); + } + // Use the internal Jaeger /dependencies API for rendering the dependency graph. if (target.queryType === 'dependencyGraph') { const timeRange = options.range ?? getDefaultTimeRange(); @@ -309,3 +310,18 @@ const emptyTraceDataFrame = new MutableDataFrame({ }, }, }); + +export function addNodeGraphFramesToResponse(response: DataQueryResponse): DataQueryResponse { + if (!response.data || response.data.length === 0) { + return response; + } + + // Convert the first frame to a DataFrame for node graph processing + const frame = toDataFrame(response.data[0]); + // Add the node graph frames to the response + const data = response.data.concat(createNodeGraphFrames(frame)); + return { + ...response, + data, + }; +} From 85a0a47efca26f8787bab5d34002ce32f4b987d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 23 Apr 2025 11:10:09 +0200 Subject: [PATCH 035/146] Dashboard: Fixes outline for repeated rows (#104283) * Dashboard: Fixes outline for repeated rows * Update * make i18n-extract --------- Co-authored-by: oscarkilhed --- .../dashboard-scene/edit-pane/shared.ts | 7 ++- .../LocalVariableEditableElement.tsx | 57 +++++++++++++++++++ .../variables/VariableEditableElement.tsx | 48 +++++++++++++++- .../variables/VariableSetEditableElement.tsx | 14 +++-- .../editors/SystemVariableEditor.tsx | 28 +++++++++ public/locales/en-US/grafana.json | 1 + 6 files changed, 147 insertions(+), 8 deletions(-) create mode 100644 public/app/features/dashboard-scene/settings/variables/LocalVariableEditableElement.tsx create mode 100644 public/app/features/dashboard-scene/settings/variables/editors/SystemVariableEditor.tsx diff --git a/public/app/features/dashboard-scene/edit-pane/shared.ts b/public/app/features/dashboard-scene/edit-pane/shared.ts index a53b35208b6..dac06f09cdd 100644 --- a/public/app/features/dashboard-scene/edit-pane/shared.ts +++ b/public/app/features/dashboard-scene/edit-pane/shared.ts @@ -1,11 +1,12 @@ import { useSessionStorage } from 'react-use'; import { BusEventWithPayload } from '@grafana/data'; -import { SceneGridRow, SceneObject, SceneVariableSet, VizPanel } from '@grafana/scenes'; +import { LocalValueVariable, SceneGridRow, SceneObject, SceneVariableSet, VizPanel } from '@grafana/scenes'; import { DashboardScene } from '../scene/DashboardScene'; import { SceneGridRowEditableElement } from '../scene/layout-default/SceneGridRowEditableElement'; import { EditableDashboardElement, isEditableDashboardElement } from '../scene/types/EditableDashboardElement'; +import { LocalVariableEditableElement } from '../settings/variables/LocalVariableEditableElement'; import { VariableEditableElement } from '../settings/variables/VariableEditableElement'; import { VariableSetEditableElement } from '../settings/variables/VariableSetEditableElement'; import { isSceneVariable } from '../settings/variables/utils'; @@ -42,6 +43,10 @@ export function getEditableElementFor(sceneObj: SceneObject | undefined): Editab return new VariableSetEditableElement(sceneObj); } + if (sceneObj instanceof LocalValueVariable) { + return new LocalVariableEditableElement(sceneObj); + } + if (isSceneVariable(sceneObj)) { return new VariableEditableElement(sceneObj); } diff --git a/public/app/features/dashboard-scene/settings/variables/LocalVariableEditableElement.tsx b/public/app/features/dashboard-scene/settings/variables/LocalVariableEditableElement.tsx new file mode 100644 index 00000000000..d5bb9103893 --- /dev/null +++ b/public/app/features/dashboard-scene/settings/variables/LocalVariableEditableElement.tsx @@ -0,0 +1,57 @@ +import { useMemo } from 'react'; + +import { LocalValueVariable } from '@grafana/scenes'; +import { Box, Stack } from '@grafana/ui'; +import { t } from 'app/core/internationalization'; +import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor'; +import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; + +import { EditableDashboardElement, EditableDashboardElementInfo } from '../../scene/types/EditableDashboardElement'; + +export class LocalVariableEditableElement implements EditableDashboardElement { + public readonly isEditableDashboardElement = true; + + public constructor(public variable: LocalValueVariable) {} + + public getEditableElementInfo(): EditableDashboardElementInfo { + return { + typeName: t('dashboard.edit-pane.elements.local-variable', 'Local variable'), + icon: 'dollar-alt', + instanceName: ` $${this.variable.state.name} = ${this.variable.getValueText!()}`, + isHidden: true, + }; + } + + public useEditPaneOptions(): OptionsPaneCategoryDescriptor[] { + const variable = this.variable; + + return useMemo(() => { + const category = new OptionsPaneCategoryDescriptor({ + title: '', + id: 'local-variable-options', + }); + + category.addItem( + new OptionsPaneItemDescriptor({ + title: '', + skipField: true, + render: () => { + return ( + + + + ${variable.state.name} + = + {variable.getValueText()} + + + + ); + }, + }) + ); + + return [category]; + }, [variable]); + } +} diff --git a/public/app/features/dashboard-scene/settings/variables/VariableEditableElement.tsx b/public/app/features/dashboard-scene/settings/variables/VariableEditableElement.tsx index b8d2ee6f281..213557fc8c4 100644 --- a/public/app/features/dashboard-scene/settings/variables/VariableEditableElement.tsx +++ b/public/app/features/dashboard-scene/settings/variables/VariableEditableElement.tsx @@ -2,8 +2,8 @@ import { FormEvent, useMemo, useState } from 'react'; import { VariableHide } from '@grafana/data'; import { locationService } from '@grafana/runtime'; -import { MultiValueVariable, SceneVariable, SceneVariableSet } from '@grafana/scenes'; -import { Input, TextArea, Button, Field, Box } from '@grafana/ui'; +import { LocalValueVariable, MultiValueVariable, SceneVariable, SceneVariableSet } from '@grafana/scenes'; +import { Input, TextArea, Button, Field, Box, Stack } from '@grafana/ui'; import { t, Trans } from 'app/core/internationalization'; import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor'; import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; @@ -24,6 +24,15 @@ export class VariableEditableElement implements EditableDashboardElement, BulkAc public constructor(public variable: SceneVariable) {} public getEditableElementInfo(): EditableDashboardElementInfo { + if (this.variable instanceof LocalValueVariable) { + return { + typeName: t('dashboard.edit-pane.elements.local-variable', 'Local variable'), + icon: 'dollar-alt', + instanceName: this.variable.state.name, + isHidden: true, + }; + } + const variableEditorDef = getEditableVariableDefinition(this.variable.state.type); return { @@ -37,6 +46,10 @@ export class VariableEditableElement implements EditableDashboardElement, BulkAc public useEditPaneOptions(isNewElement: boolean): OptionsPaneCategoryDescriptor[] { const variable = this.variable; + if (variable instanceof LocalValueVariable) { + return useLocalVariableOptions(variable); + } + const basicOptions = useMemo(() => { return new OptionsPaneCategoryDescriptor({ title: '', id: 'variable-options' }) .addItem( @@ -220,3 +233,34 @@ function OpenOldVariableEditButton({ variable }: VariableInputProps) { ); } + +function useLocalVariableOptions(variable: LocalValueVariable): OptionsPaneCategoryDescriptor[] { + return useMemo(() => { + const category = new OptionsPaneCategoryDescriptor({ + title: '', + id: 'local-variable-options', + }); + + category.addItem( + new OptionsPaneItemDescriptor({ + title: '', + skipField: true, + render: () => { + return ( + + + + ${variable.state.name} + = + {variable.getValueText()} + + + + ); + }, + }) + ); + + return [category]; + }, [variable]); +} diff --git a/public/app/features/dashboard-scene/settings/variables/VariableSetEditableElement.tsx b/public/app/features/dashboard-scene/settings/variables/VariableSetEditableElement.tsx index a1e4047901f..442f7c58b47 100644 --- a/public/app/features/dashboard-scene/settings/variables/VariableSetEditableElement.tsx +++ b/public/app/features/dashboard-scene/settings/variables/VariableSetEditableElement.tsx @@ -10,6 +10,7 @@ import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; import { NewObjectAddedToCanvasEvent } from '../../edit-pane/shared'; +import { DashboardScene } from '../../scene/DashboardScene'; import { EditableDashboardElement, EditableDashboardElementInfo } from '../../scene/types/EditableDashboardElement'; import { getDashboardSceneFor } from '../../utils/utils'; @@ -51,6 +52,7 @@ function VariableList({ set }: { set: SceneVariableSet }) { const { variables } = set.useState(); const styles = useStyles2(getStyles); const [isAdding, setIsAdding] = useToggle(false); + const canAdd = set.parent instanceof DashboardScene; const onEditVariable = (variable: SceneVariable) => { const { editPane } = getDashboardSceneFor(set).state; @@ -83,11 +85,13 @@ function VariableList({ set }: { set: SceneVariableSet }) {
))} - - - + {canAdd && ( + + + + )} ); } diff --git a/public/app/features/dashboard-scene/settings/variables/editors/SystemVariableEditor.tsx b/public/app/features/dashboard-scene/settings/variables/editors/SystemVariableEditor.tsx new file mode 100644 index 00000000000..d7a55ae5dad --- /dev/null +++ b/public/app/features/dashboard-scene/settings/variables/editors/SystemVariableEditor.tsx @@ -0,0 +1,28 @@ +import { SceneVariable, LocalValueVariable } from '@grafana/scenes'; +import { Stack } from '@grafana/ui'; +import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; + +export function getSystemVariableOptions(variable: SceneVariable): OptionsPaneItemDescriptor[] { + if (!(variable instanceof LocalValueVariable)) { + return []; + } + + return [ + new OptionsPaneItemDescriptor({ + title: '', + render: () => { + return ( + + + + ${variable.state.name} + = + ${variable.getValueText()} + + + + ); + }, + }), + ]; +} diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 01564808222..f56e7ceb411 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -3173,6 +3173,7 @@ "edit-pane": { "elements": { "dashboard": "Dashboard", + "local-variable": "Local variable", "multiple-elements": "Multiple elements", "multiple-elements-delete-text": "Are you sure you want to delete these elements?", "multiple-panels": "Multiple panels", From 5a50c76f58dbc787f3b79a98f1768eac197c72fd Mon Sep 17 00:00:00 2001 From: marybelvargas <107340764+marybelvargas@users.noreply.github.com> Date: Wed, 23 Apr 2025 04:15:06 -0500 Subject: [PATCH 036/146] [Docs - RBAC] Update docs to reflect new fixed role granted to the Viewer basic role (#104353) Co-authored-by: Irene Rodriguez --- .../access-control/rbac-fixed-basic-role-definitions/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md b/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md index 49352d7a600..6b00657fd54 100644 --- a/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md +++ b/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md @@ -59,7 +59,7 @@ The following tables list permissions associated with basic and fixed roles. Thi | Grafana Admin | `basic_grafana_admin` | `fixed:roles:reader`
`fixed:roles:writer`
`fixed:users:reader`
`fixed:users:writer`
`fixed:org.users:reader`
`fixed:org.users:writer`
`fixed:ldap:reader`
`fixed:ldap:writer`
`fixed:stats:reader`
`fixed:settings:reader`
`fixed:settings:writer`
`fixed:provisioning:writer`
`fixed:organization:reader`
`fixed:organization:maintainer`
`fixed:licensing:reader`
`fixed:licensing:writer`
`fixed:datasources.caching:reader`
`fixed:datasources.caching:writer`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader`
`fixed:plugins:maintainer`
`fixed:authentication.config:writer`
`fixed:library.panels:creator`
`fixed:library.panels:reader`
`fixed:library.panels:general.reader`
`fixed:library.panels:writer`
`fixed:library.panels:general.writer`
`fixed:migrationassistant:migrator` | Default [Grafana server administrator](/docs/grafana//administration/roles-and-permissions/#grafana-server-administrators) assignments. | | Admin | `basic_admin` | `fixed:reports:reader`
`fixed:reports:writer`
`fixed:datasources:reader`
`fixed:datasources:writer`
`fixed:organization:writer`
`fixed:datasources.permissions:reader`
`fixed:datasources.permissions:writer`
`fixed:teams:writer`
`fixed:dashboards:reader`
`fixed:dashboards:writer`
`fixed:dashboards.permissions:reader`
`fixed:dashboards.permissions:writer`
`fixed:dashboards.public:writer`
`fixed:folders:reader`
`fixed:folders:writer`
`fixed:folders.permissions:reader`
`fixed:folders.permissions:writer`
`fixed:alerting:writer`
`fixed:apikeys:reader`
`fixed:apikeys:writer`
`fixed:alerting.provisioning.secrets:reader`
`fixed:alerting.provisioning:writer`
`fixed:datasources.caching:reader`
`fixed:datasources.caching:writer`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader`
`fixed:plugins:writer`
`fixed:library.panels:creator`
`fixed:library.panels:reader`
`fixed:library.panels:general.reader`
`fixed:library.panels:writer`
`fixed:library.panels:general.writer`
`fixed:alerting.provisioning.status:writer` | Default [Grafana organization administrator](ref:rbac-basic-roles) assignments. | | Editor | `basic_editor` | `fixed:datasources:explorer`
`fixed:dashboards:creator`
`fixed:folders:creator`
`fixed:annotations:writer`
`fixed:alerting:writer`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader`
`fixed:library.panels:creator`
`fixed:library.panels:general.reader`
`fixed:library.panels:general.writer`
`fixed:alerting.provisioning.status:writer` | Default [Editor](ref:rbac-basic-roles) assignments. | -| Viewer | `basic_viewer` | `fixed:datasources.id:reader`
`fixed:organization:reader`
`fixed:annotations:reader`
`fixed:annotations.dashboard:writer`
`fixed:alerting:reader`
`fixed:plugins.app:reader`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader`
`fixed:library.panels:general.reader` | Default [Viewer](ref:rbac-basic-roles) assignments. | +| Viewer | `basic_viewer` | `fixed:datasources.id:reader`
`fixed:organization:reader`
`fixed:annotations:reader`
`fixed:annotations.dashboard:writer`
`fixed:alerting:reader`
`fixed:plugins.app:reader`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader`
`fixed:library.panels:general.reader`
`fixed:folders.general:reader` | Default [Viewer](ref:rbac-basic-roles) assignments. | | No Basic Role | n/a | | Default [No Basic Role](ref:rbac-basic-roles) | ## Fixed role definitions From 9a7cb96eb0825da750456b6b861379f15a52fa0a Mon Sep 17 00:00:00 2001 From: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com> Date: Wed, 23 Apr 2025 11:17:09 +0200 Subject: [PATCH 037/146] Alerting: Allow clearing namespace and group filter (#104376) allow clearing namespace and group filter --- .../components/import-to-gma/NamespaceAndGroupFilter.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/public/app/features/alerting/unified/components/import-to-gma/NamespaceAndGroupFilter.tsx b/public/app/features/alerting/unified/components/import-to-gma/NamespaceAndGroupFilter.tsx index 6cab25cb808..73bc85ed022 100644 --- a/public/app/features/alerting/unified/components/import-to-gma/NamespaceAndGroupFilter.tsx +++ b/public/app/features/alerting/unified/components/import-to-gma/NamespaceAndGroupFilter.tsx @@ -59,7 +59,7 @@ export const NamespaceAndGroupFilter = ({ rulesSourceName }: Props) => { {...field} onChange={(value) => { setValue('ruleGroup', ''); //reset if namespace changes - onChange(value.value); + onChange(value?.value); }} id="namespace-picker" placeholder={t('alerting.namespace-and-group-filter.select-namespace', 'Select namespace')} @@ -67,6 +67,7 @@ export const NamespaceAndGroupFilter = ({ rulesSourceName }: Props) => { width={42} loading={isLoading} disabled={isLoading || !rulesSourceName} + isClearable /> )} name="namespace" @@ -88,12 +89,13 @@ export const NamespaceAndGroupFilter = ({ rulesSourceName }: Props) => { options={groupOptions} width={42} onChange={(value) => { - setValue('ruleGroup', value.value ?? ''); + setValue('ruleGroup', value?.value ?? ''); }} id="group-picker" placeholder={t('alerting.namespace-and-group-filter.select-group', 'Select group')} loading={isLoading} disabled={isLoading || !namespace || !rulesSourceName} + isClearable /> )} name="ruleGroup" From f9fb6f3b884874db9335dfe217dc5c3b1b9a7f60 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Wed, 23 Apr 2025 10:24:26 +0100 Subject: [PATCH 038/146] Chore: Update eslint configs to ignore `/spec/` files when necessary (#104377) --- .betterer.eslint.config.js | 10 ++++++++-- .betterer.results | 3 +-- eslint.config.js | 1 + public/app/features/explore/spec/helper/setup.tsx | 5 +---- public/locales/en-US/grafana.json | 3 --- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.betterer.eslint.config.js b/.betterer.eslint.config.js index 3030dd53c3e..d07e461ea2a 100644 --- a/.betterer.eslint.config.js +++ b/.betterer.eslint.config.js @@ -96,8 +96,14 @@ module.exports = [ }, }, { - files: ['**/*.{ts,tsx}'], - ignores: ['**/*.{test,spec}.{ts,tsx}', '**/__mocks__/**', '**/public/test/**', '**/mocks.{ts,tsx}'], + files: ['**/*.{js,jsx,ts,tsx}'], + ignores: [ + '**/*.{test,spec}.{ts,tsx}', + '**/__mocks__/**', + '**/public/test/**', + '**/mocks.{ts,tsx}', + '**/spec/**/*.{ts,tsx}', + ], rules: { '@typescript-eslint/consistent-type-assertions': ['error', { assertionStyle: 'never' }], }, diff --git a/.betterer.results b/.betterer.results index 8effcfa4d09..f84c6215f19 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1723,8 +1723,7 @@ exports[`better eslint`] = { [0, 0, 0, "Do not re-export imported variable (\`./external.utils\`)", "0"] ], "public/app/features/explore/spec/helper/setup.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"] + [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], "public/app/features/explore/state/time.test.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] diff --git a/eslint.config.js b/eslint.config.js index 5a314282773..e4bd3d8e406 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -284,6 +284,7 @@ module.exports = [ '**/*.{test,spec}.{ts,tsx}', '**/__mocks__/', 'public/test', + '**/spec/**/*.{ts,tsx}', ], rules: { '@grafana/no-untranslated-strings': 'error', diff --git a/public/app/features/explore/spec/helper/setup.tsx b/public/app/features/explore/spec/helper/setup.tsx index 3573e7adb34..9b5870691e3 100644 --- a/public/app/features/explore/spec/helper/setup.tsx +++ b/public/app/features/explore/spec/helper/setup.tsx @@ -34,7 +34,6 @@ import { import { DataSourceRef } from '@grafana/schema'; import { AppChrome } from 'app/core/components/AppChrome/AppChrome'; import { GrafanaContext } from 'app/core/context/GrafanaContext'; -import { t } from 'app/core/internationalization'; import { GrafanaRoute } from 'app/core/navigation/GrafanaRoute'; import { Echo } from 'app/core/services/echo/Echo'; import { setLastUsedDatasourceUID } from 'app/core/utils/explore'; @@ -291,11 +290,9 @@ export function makeDatasourceSetup({ components: { QueryEditor(props: QueryEditorProps) { return ( - // don't need translations, this is a test helper - // eslint-disable-next-line @grafana/no-untranslated-strings
{ props.onChange({ ...props.query, expr: event.target.value }); diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index f56e7ceb411..8ec3abefc96 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -4739,9 +4739,6 @@ "logs-volumne-panel-list": { "body-no-logs-volume-available": "No volume information available for the current queries and time range." }, - "make-datasource-setup": { - "aria-label-query": "query" - }, "next": "Next", "next-prev-result": { "aria-label-next": "Next result button", From 4b48121464cede9751d8c419c937237f89b35ad4 Mon Sep 17 00:00:00 2001 From: Sven Grossmann Date: Wed, 23 Apr 2025 11:30:59 +0200 Subject: [PATCH 039/146] Investigations: Allow everyone with `datasources:explore` permissions to use investigations (#104280) * Investigations: Authorize everyone to create investigations * fix test --- apps/investigations/pkg/app/authorizer.go | 38 ++++++++ .../investigations/pkg/app/authorizer_test.go | 87 +++++++++++++++++++ .../pkg/app/investigations_app.go | 2 +- pkg/registry/apps/investigations/register.go | 1 + 4 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 apps/investigations/pkg/app/authorizer.go create mode 100644 apps/investigations/pkg/app/authorizer_test.go diff --git a/apps/investigations/pkg/app/authorizer.go b/apps/investigations/pkg/app/authorizer.go new file mode 100644 index 00000000000..0747b06da5e --- /dev/null +++ b/apps/investigations/pkg/app/authorizer.go @@ -0,0 +1,38 @@ +package app + +import ( + "context" + + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/services/accesscontrol" + + "k8s.io/apiserver/pkg/authorization/authorizer" +) + +func GetAuthorizer() authorizer.Authorizer { + return authorizer.AuthorizerFunc(func( + ctx context.Context, attr authorizer.Attributes, + ) (authorized authorizer.Decision, reason string, err error) { + if !attr.IsResourceRequest() { + return authorizer.DecisionNoOpinion, "", nil + } + + u, err := identity.GetRequester(ctx) + if err != nil { + return authorizer.DecisionDeny, "valid user is required", err + } + + p := u.GetPermissions() + if len(p) == 0 { + return authorizer.DecisionDeny, "no permissions", nil + } + + _, ok := p[accesscontrol.ActionDatasourcesExplore] + if !ok { + // defer to the default authorizer if datasources:explore is not present + return authorizer.DecisionNoOpinion, "", nil + } + + return authorizer.DecisionAllow, "", nil + }) +} diff --git a/apps/investigations/pkg/app/authorizer_test.go b/apps/investigations/pkg/app/authorizer_test.go new file mode 100644 index 00000000000..9829fb84793 --- /dev/null +++ b/apps/investigations/pkg/app/authorizer_test.go @@ -0,0 +1,87 @@ +package app + +import ( + "context" + "testing" + + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/stretchr/testify/assert" + "k8s.io/apiserver/pkg/authorization/authorizer" +) + +func TestGetAuthorizer(t *testing.T) { + tests := []struct { + name string + ctx context.Context + attr authorizer.Attributes + expectedDecision authorizer.Decision + expectedReason string + expectedErr error + }{ + { + name: "non-resource request", + ctx: context.TODO(), + attr: &mockAttributes{resourceRequest: false}, + expectedDecision: authorizer.DecisionNoOpinion, + expectedReason: "", + expectedErr: nil, + }, + { + name: "user has datasources:explore permission", + ctx: identity.WithRequester(context.TODO(), &mockUser{permissions: map[string][]string{accesscontrol.ActionDatasourcesExplore: {}}}), + attr: &mockAttributes{resourceRequest: true}, + expectedDecision: authorizer.DecisionAllow, + expectedReason: "", + expectedErr: nil, + }, + { + name: "user does not have datasources:explore permission", + ctx: identity.WithRequester(context.TODO(), &mockUser{}), + attr: &mockAttributes{resourceRequest: true}, + expectedDecision: authorizer.DecisionDeny, + expectedReason: "no permissions", + expectedErr: nil, + }, + { + name: "user does not have datasources:explore permission", + ctx: identity.WithRequester(context.TODO(), &mockUser{permissions: map[string][]string{"foo": {}}}), + attr: &mockAttributes{resourceRequest: true}, + expectedDecision: authorizer.DecisionNoOpinion, + expectedReason: "", + expectedErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + auth := GetAuthorizer() + decision, reason, err := auth.Authorize(tt.ctx, tt.attr) + assert.Equal(t, tt.expectedDecision, decision) + assert.Equal(t, tt.expectedReason, reason) + assert.Equal(t, tt.expectedErr, err) + }) + } +} + +type mockAttributes struct { + authorizer.Attributes + resourceRequest bool +} + +func (m *mockAttributes) IsResourceRequest() bool { + return m.resourceRequest +} + +// Implement other methods of authorizer.Attributes as needed + +type mockUser struct { + identity.Requester + permissions map[string][]string +} + +func (m *mockUser) GetPermissions() map[string][]string { + return m.permissions +} + +// Implement other methods of identity.Requester as needed diff --git a/apps/investigations/pkg/app/investigations_app.go b/apps/investigations/pkg/app/investigations_app.go index 8ec35c1ed19..3155eb88404 100644 --- a/apps/investigations/pkg/app/investigations_app.go +++ b/apps/investigations/pkg/app/investigations_app.go @@ -18,7 +18,7 @@ func New(cfg app.Config) (app.App, error) { Name: "investigation", KubeConfig: cfg.KubeConfig, InformerConfig: simple.AppInformerConfig{ - ErrorHandler: func(ctx context.Context, err error) { + ErrorHandler: func(_ context.Context, err error) { klog.ErrorS(err, "Informer processing error") }, }, diff --git a/pkg/registry/apps/investigations/register.go b/pkg/registry/apps/investigations/register.go index 83afeee6fc1..ea4a2fac98e 100644 --- a/pkg/registry/apps/investigations/register.go +++ b/pkg/registry/apps/investigations/register.go @@ -24,6 +24,7 @@ func RegisterApp( appCfg := &runner.AppBuilderConfig{ OpenAPIDefGetter: investigationv0alpha1.GetOpenAPIDefinitions, ManagedKinds: investigationapp.GetKinds(), + Authorizer: investigationapp.GetAuthorizer(), } provider.Provider = simple.NewAppProvider(apis.LocalManifest(), appCfg, investigationapp.New) return provider From 7e192e319c3826f67166c1b6421d2e2cb3dfb584 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 23 Apr 2025 12:46:34 +0300 Subject: [PATCH 040/146] Devenv: Avoid explicit uid for testdata in dashboards (#104338) --- .../annotations/annotation-filtering.json | 20 +---- .../templating-repeating-panels.json | 10 +-- .../templating-repeating-rows.json | 5 +- devenv/dev-dashboards/home.json | 30 ++------ .../dev-dashboards/migrations/migrations.json | 75 ++++--------------- .../barchart-thresholds-mappings.json | 60 +++------------ .../barchart-tooltips-legends.json | 10 +-- .../panel-bargauge/panel_tests_bar_gauge.json | 65 ++++------------ .../panel-candlestick/candlestick.json | 20 +---- .../canvas-connection-examples.json | 10 +-- .../panel-canvas/canvas-examples.json | 40 ++-------- .../panel-common/shared_queries.json | 5 +- .../datagrid_metric_values.json | 5 +- .../panel_tests_flame_graph.json | 5 +- .../panel-geomap/geomap-color-field.json | 5 +- .../panel-geomap/geomap-photo-layer.json | 5 +- .../panel-geomap/geomap-route-layer.json | 5 +- ...geomap-spatial-operations-transformer.json | 5 +- .../panel-geomap/geomap-v91.json | 15 +--- .../panel-geomap/geomap_multi-layers.json | 10 +-- .../panel-geomap/panel-geomap.json | 20 +---- .../panel-heatmap/heatmap-calculate-log.json | 5 +- .../panel-heatmap/heatmap-legacy.json | 25 ++----- .../panel-histogram/histogram_tests.json | 40 ++-------- .../panel-table/table_sparkline_cell.json | 5 +- .../panel-table/table_tests_new.json | 15 +--- .../panel-text/text-options.json | 20 +---- .../panel-timeline/timeline-demo.json | 20 +---- .../panel-timeline/timeline-modes.json | 15 +--- .../timeline-thresholds-mappings.json | 40 ++-------- .../panel-timeseries/timeseries-formats.json | 10 +-- .../panel-timeseries/timeseries-nulls.json | 50 +++---------- .../timeseries-stacking2.json | 70 ++++------------- .../timeseries-yaxis-ticks.json | 70 ++++------------- .../panel-trend/trend_example.json | 5 +- .../panel-xychart/xychart-migrations.json | 50 +++---------- .../xychart-tooltip-color-test.json | 5 +- .../transforms/join-by-field.json | 10 +-- .../transforms/join-by-labels.json | 5 +- 39 files changed, 177 insertions(+), 708 deletions(-) diff --git a/devenv/dev-dashboards/annotations/annotation-filtering.json b/devenv/dev-dashboards/annotations/annotation-filtering.json index e27999e26ec..a827d960d76 100644 --- a/devenv/dev-dashboards/annotations/annotation-filtering.json +++ b/devenv/dev-dashboards/annotations/annotation-filtering.json @@ -101,10 +101,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -179,10 +176,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -257,10 +251,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -335,10 +326,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/feature-templating/templating-repeating-panels.json b/devenv/dev-dashboards/feature-templating/templating-repeating-panels.json index 2bea5930a49..ee69cd7e110 100644 --- a/devenv/dev-dashboards/feature-templating/templating-repeating-panels.json +++ b/devenv/dev-dashboards/feature-templating/templating-repeating-panels.json @@ -42,10 +42,7 @@ "type": "text" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -159,10 +156,7 @@ "type": "text" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/feature-templating/templating-repeating-rows.json b/devenv/dev-dashboards/feature-templating/templating-repeating-rows.json index 11c9733abd8..af630afd1f5 100644 --- a/devenv/dev-dashboards/feature-templating/templating-repeating-rows.json +++ b/devenv/dev-dashboards/feature-templating/templating-repeating-rows.json @@ -69,10 +69,7 @@ "type": "row" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/home.json b/devenv/dev-dashboards/home.json index 840d32919ea..410e1bcf290 100644 --- a/devenv/dev-dashboards/home.json +++ b/devenv/dev-dashboards/home.json @@ -29,10 +29,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 26, "w": 6, @@ -56,10 +53,7 @@ "type": "dashlist" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 13, "w": 6, @@ -87,10 +81,7 @@ "type": "dashlist" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 13, "w": 6, @@ -120,10 +111,7 @@ "type": "dashlist" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 26, "w": 6, @@ -153,10 +141,7 @@ "type": "dashlist" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 13, "w": 6, @@ -186,10 +171,7 @@ "type": "dashlist" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 13, "w": 6, diff --git a/devenv/dev-dashboards/migrations/migrations.json b/devenv/dev-dashboards/migrations/migrations.json index 5abfe8be420..d872f4e6893 100644 --- a/devenv/dev-dashboards/migrations/migrations.json +++ b/devenv/dev-dashboards/migrations/migrations.json @@ -195,10 +195,7 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "unit": "short" @@ -291,10 +288,7 @@ } }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 11, "w": 8, @@ -329,10 +323,7 @@ "bars": true, "dashLength": 10, "dashes": false, - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "unit": "short" @@ -429,10 +420,7 @@ } }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 11, "w": 8, @@ -568,10 +556,7 @@ } }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 11, "w": 8, @@ -606,10 +591,7 @@ "bars": true, "dashLength": 10, "dashes": false, - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "unit": "short" @@ -704,10 +686,7 @@ } }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 11, "w": 8, @@ -739,10 +718,7 @@ }, { "columns": [], - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fontSize": "100%", "gridPos": { "h": 10, @@ -816,10 +792,7 @@ "type": "table-old" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 10, "w": 8, @@ -857,10 +830,7 @@ "rgba(237, 129, 40, 0.89)", "#d44a3a" ], - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "format": "areaF2", "gauge": { "maxValue": 100, @@ -930,10 +900,7 @@ "valueName": "avg" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "mappings": [ @@ -1002,10 +969,7 @@ "type": "stat" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 8, "w": 8, @@ -1036,10 +1000,7 @@ "type": "text" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 10, "w": 16, @@ -1089,10 +1050,7 @@ "type": "grafana-piechart-panel" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 10, "w": 8, @@ -1267,10 +1225,7 @@ "valueName": "total" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 10, "w": 8, diff --git a/devenv/dev-dashboards/panel-barchart/barchart-thresholds-mappings.json b/devenv/dev-dashboards/panel-barchart/barchart-thresholds-mappings.json index 6aaa42dddc6..81138940b74 100644 --- a/devenv/dev-dashboards/panel-barchart/barchart-thresholds-mappings.json +++ b/devenv/dev-dashboards/panel-barchart/barchart-thresholds-mappings.json @@ -28,10 +28,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -124,10 +121,7 @@ "type": "barchart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -244,10 +238,7 @@ "type": "barchart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -328,10 +319,7 @@ "type": "barchart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -413,10 +401,7 @@ "type": "barchart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -542,10 +527,7 @@ "type": "barchart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -684,10 +666,7 @@ "type": "barchart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -805,10 +784,7 @@ "type": "barchart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -948,10 +924,7 @@ "type": "barchart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -1035,10 +1008,7 @@ "type": "barchart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -1122,10 +1092,7 @@ "type": "barchart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -1209,10 +1176,7 @@ "type": "barchart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-barchart/barchart-tooltips-legends.json b/devenv/dev-dashboards/panel-barchart/barchart-tooltips-legends.json index 6a0046a0ed9..7dc1b0ee08c 100644 --- a/devenv/dev-dashboards/panel-barchart/barchart-tooltips-legends.json +++ b/devenv/dev-dashboards/panel-barchart/barchart-tooltips-legends.json @@ -358,10 +358,7 @@ "type": "row" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -465,10 +462,7 @@ "type": "barchart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-bargauge/panel_tests_bar_gauge.json b/devenv/dev-dashboards/panel-bargauge/panel_tests_bar_gauge.json index 3dfa360c740..cc8f600140f 100644 --- a/devenv/dev-dashboards/panel-bargauge/panel_tests_bar_gauge.json +++ b/devenv/dev-dashboards/panel-bargauge/panel_tests_bar_gauge.json @@ -28,10 +28,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -125,10 +122,7 @@ "type": "bargauge" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -238,10 +232,7 @@ "type": "bargauge" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -351,10 +342,7 @@ "type": "bargauge" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -464,10 +452,7 @@ "type": "bargauge" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -577,10 +562,7 @@ "type": "bargauge" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -690,10 +672,7 @@ "type": "bargauge" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -845,10 +824,7 @@ "type": "bargauge" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -1023,10 +999,7 @@ "type": "bargauge" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -1096,10 +1069,7 @@ "type": "bargauge" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -1169,10 +1139,7 @@ "type": "bargauge" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -1242,10 +1209,7 @@ "type": "bargauge" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -1320,10 +1284,7 @@ "type": "bargauge" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-candlestick/candlestick.json b/devenv/dev-dashboards/panel-candlestick/candlestick.json index cac7c361c16..63a57ad394b 100644 --- a/devenv/dev-dashboards/panel-candlestick/candlestick.json +++ b/devenv/dev-dashboards/panel-candlestick/candlestick.json @@ -29,10 +29,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -177,10 +174,7 @@ "type": "candlestick" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -266,10 +260,7 @@ "type": "candlestick" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -355,10 +346,7 @@ "type": "candlestick" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-canvas/canvas-connection-examples.json b/devenv/dev-dashboards/panel-canvas/canvas-connection-examples.json index 784bfb2c865..aedd6be604f 100644 --- a/devenv/dev-dashboards/panel-canvas/canvas-connection-examples.json +++ b/devenv/dev-dashboards/panel-canvas/canvas-connection-examples.json @@ -2225,10 +2225,7 @@ "type": "canvas" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -2857,10 +2854,7 @@ "type": "canvas" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-canvas/canvas-examples.json b/devenv/dev-dashboards/panel-canvas/canvas-examples.json index 630d78df5eb..7b025da055f 100644 --- a/devenv/dev-dashboards/panel-canvas/canvas-examples.json +++ b/devenv/dev-dashboards/panel-canvas/canvas-examples.json @@ -50,10 +50,7 @@ }, "type": "canvas", "title": "Wind Energy", - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "pluginVersion": "9.4.0-pre", "fieldConfig": { "defaults": { @@ -1144,10 +1141,7 @@ ] }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 15, "w": 6, @@ -1182,10 +1176,7 @@ "type": "row" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -3330,10 +3321,7 @@ "type": "canvas" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 18, "w": 6, @@ -3368,10 +3356,7 @@ "type": "row" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -3608,10 +3593,7 @@ "type": "canvas" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 15, "w": 6, @@ -3633,10 +3615,7 @@ "type": "text" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -3824,10 +3803,7 @@ "type": "canvas" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 15, "w": 6, diff --git a/devenv/dev-dashboards/panel-common/shared_queries.json b/devenv/dev-dashboards/panel-common/shared_queries.json index 463e139c62e..2117b83a399 100644 --- a/devenv/dev-dashboards/panel-common/shared_queries.json +++ b/devenv/dev-dashboards/panel-common/shared_queries.json @@ -22,10 +22,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-datagrid/datagrid_metric_values.json b/devenv/dev-dashboards/panel-datagrid/datagrid_metric_values.json index 977a32e923c..00a65a9f1aa 100644 --- a/devenv/dev-dashboards/panel-datagrid/datagrid_metric_values.json +++ b/devenv/dev-dashboards/panel-datagrid/datagrid_metric_values.json @@ -22,10 +22,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 8, "w": 12, diff --git a/devenv/dev-dashboards/panel-flamegraph/panel_tests_flame_graph.json b/devenv/dev-dashboards/panel-flamegraph/panel_tests_flame_graph.json index 7eedd9dd088..efd4ecdb27c 100644 --- a/devenv/dev-dashboards/panel-flamegraph/panel_tests_flame_graph.json +++ b/devenv/dev-dashboards/panel-flamegraph/panel_tests_flame_graph.json @@ -28,10 +28,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 14, "w": 24, diff --git a/devenv/dev-dashboards/panel-geomap/geomap-color-field.json b/devenv/dev-dashboards/panel-geomap/geomap-color-field.json index 1f384f01b72..38ea1d23b39 100644 --- a/devenv/dev-dashboards/panel-geomap/geomap-color-field.json +++ b/devenv/dev-dashboards/panel-geomap/geomap-color-field.json @@ -28,10 +28,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-geomap/geomap-photo-layer.json b/devenv/dev-dashboards/panel-geomap/geomap-photo-layer.json index 5f129330534..af192c66d59 100644 --- a/devenv/dev-dashboards/panel-geomap/geomap-photo-layer.json +++ b/devenv/dev-dashboards/panel-geomap/geomap-photo-layer.json @@ -29,10 +29,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-geomap/geomap-route-layer.json b/devenv/dev-dashboards/panel-geomap/geomap-route-layer.json index c6b1752bd2a..169ca0163c5 100644 --- a/devenv/dev-dashboards/panel-geomap/geomap-route-layer.json +++ b/devenv/dev-dashboards/panel-geomap/geomap-route-layer.json @@ -92,10 +92,7 @@ "csvContent": "lat,lon,alt\n45,0,0\n40,5,5\n35,10,10\n30,15, 15\n25,20,20" } ], - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "options": { "view": { "allLayers": true, diff --git a/devenv/dev-dashboards/panel-geomap/geomap-spatial-operations-transformer.json b/devenv/dev-dashboards/panel-geomap/geomap-spatial-operations-transformer.json index 2b720c74b12..0bd5732b577 100644 --- a/devenv/dev-dashboards/panel-geomap/geomap-spatial-operations-transformer.json +++ b/devenv/dev-dashboards/panel-geomap/geomap-spatial-operations-transformer.json @@ -28,10 +28,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-geomap/geomap-v91.json b/devenv/dev-dashboards/panel-geomap/geomap-v91.json index daadd7b8ec8..d9a928ceba1 100644 --- a/devenv/dev-dashboards/panel-geomap/geomap-v91.json +++ b/devenv/dev-dashboards/panel-geomap/geomap-v91.json @@ -28,10 +28,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -129,10 +126,7 @@ "type": "geomap" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -215,10 +209,7 @@ "type": "geomap" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-geomap/geomap_multi-layers.json b/devenv/dev-dashboards/panel-geomap/geomap_multi-layers.json index a1b37992f78..8cecfcab846 100644 --- a/devenv/dev-dashboards/panel-geomap/geomap_multi-layers.json +++ b/devenv/dev-dashboards/panel-geomap/geomap_multi-layers.json @@ -29,10 +29,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -322,10 +319,7 @@ "type": "geomap" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-geomap/panel-geomap.json b/devenv/dev-dashboards/panel-geomap/panel-geomap.json index f2d65af697a..70f1595204f 100644 --- a/devenv/dev-dashboards/panel-geomap/panel-geomap.json +++ b/devenv/dev-dashboards/panel-geomap/panel-geomap.json @@ -28,10 +28,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -130,10 +127,7 @@ "type": "geomap" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -236,10 +230,7 @@ "type": "geomap" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -334,10 +325,7 @@ "type": "geomap" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-heatmap/heatmap-calculate-log.json b/devenv/dev-dashboards/panel-heatmap/heatmap-calculate-log.json index ad92327a753..f48358f7a02 100644 --- a/devenv/dev-dashboards/panel-heatmap/heatmap-calculate-log.json +++ b/devenv/dev-dashboards/panel-heatmap/heatmap-calculate-log.json @@ -28,10 +28,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-heatmap/heatmap-legacy.json b/devenv/dev-dashboards/panel-heatmap/heatmap-legacy.json index f96382242e1..d42005d2a59 100644 --- a/devenv/dev-dashboards/panel-heatmap/heatmap-legacy.json +++ b/devenv/dev-dashboards/panel-heatmap/heatmap-legacy.json @@ -40,10 +40,7 @@ "mode": "opacity" }, "dataFormat": "tsbuckets", - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 11, "w": 7, @@ -96,10 +93,7 @@ "mode": "spectrum" }, "dataFormat": "timeseries", - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 11, "w": 9, @@ -157,10 +151,7 @@ "mode": "spectrum" }, "dataFormat": "timeseries", - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 11, "w": 8, @@ -213,10 +204,7 @@ "mode": "opacity" }, "dataFormat": "tsbuckets", - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 11, "w": 7, @@ -327,10 +315,7 @@ "mode": "spectrum" }, "dataFormat": "timeseries", - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 11, "w": 8, diff --git a/devenv/dev-dashboards/panel-histogram/histogram_tests.json b/devenv/dev-dashboards/panel-histogram/histogram_tests.json index 0c75888c4a3..eb5d6374971 100644 --- a/devenv/dev-dashboards/panel-histogram/histogram_tests.json +++ b/devenv/dev-dashboards/panel-histogram/histogram_tests.json @@ -28,10 +28,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -168,10 +165,7 @@ "type": "histogram" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -251,10 +245,7 @@ "type": "histogram" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -334,10 +325,7 @@ "type": "histogram" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -429,10 +417,7 @@ "type": "table" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -516,10 +501,7 @@ "type": "table" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -601,10 +583,7 @@ "type": "histogram" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -686,10 +665,7 @@ "type": "histogram" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-table/table_sparkline_cell.json b/devenv/dev-dashboards/panel-table/table_sparkline_cell.json index ee1a99447e8..d18488ca89d 100644 --- a/devenv/dev-dashboards/panel-table/table_sparkline_cell.json +++ b/devenv/dev-dashboards/panel-table/table_sparkline_cell.json @@ -22,10 +22,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-table/table_tests_new.json b/devenv/dev-dashboards/panel-table/table_tests_new.json index c87e2b799e6..69b50b9b23e 100644 --- a/devenv/dev-dashboards/panel-table/table_tests_new.json +++ b/devenv/dev-dashboards/panel-table/table_tests_new.json @@ -182,10 +182,7 @@ "type": "table" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -332,10 +329,7 @@ "type": "table" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -472,10 +466,7 @@ "type": "table" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-text/text-options.json b/devenv/dev-dashboards/panel-text/text-options.json index af1c758a86e..696bda08d52 100644 --- a/devenv/dev-dashboards/panel-text/text-options.json +++ b/devenv/dev-dashboards/panel-text/text-options.json @@ -29,10 +29,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 9, "w": 12, @@ -64,10 +61,7 @@ "type": "text" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 9, "w": 12, @@ -99,10 +93,7 @@ "type": "text" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 9, "w": 12, @@ -134,10 +125,7 @@ "type": "text" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 9, "w": 12, diff --git a/devenv/dev-dashboards/panel-timeline/timeline-demo.json b/devenv/dev-dashboards/panel-timeline/timeline-demo.json index f974f202a1b..e25aa23ce1c 100644 --- a/devenv/dev-dashboards/panel-timeline/timeline-demo.json +++ b/devenv/dev-dashboards/panel-timeline/timeline-demo.json @@ -29,10 +29,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -131,10 +128,7 @@ "type": "state-timeline" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -237,10 +231,7 @@ "type": "state-timeline" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "description": "Should show gaps", "fieldConfig": { "defaults": { @@ -343,10 +334,7 @@ "type": "state-timeline" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-timeline/timeline-modes.json b/devenv/dev-dashboards/panel-timeline/timeline-modes.json index 76294a090b2..7c4d9ee05c3 100644 --- a/devenv/dev-dashboards/panel-timeline/timeline-modes.json +++ b/devenv/dev-dashboards/panel-timeline/timeline-modes.json @@ -29,10 +29,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -211,10 +208,7 @@ "type": "state-timeline" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -288,10 +282,7 @@ "type": "state-timeline" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-timeline/timeline-thresholds-mappings.json b/devenv/dev-dashboards/panel-timeline/timeline-thresholds-mappings.json index 3840f914821..137bbfe04f2 100644 --- a/devenv/dev-dashboards/panel-timeline/timeline-thresholds-mappings.json +++ b/devenv/dev-dashboards/panel-timeline/timeline-thresholds-mappings.json @@ -29,10 +29,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -96,10 +93,7 @@ "type": "state-timeline" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -171,10 +165,7 @@ "type": "state-timeline" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -246,10 +237,7 @@ "type": "state-timeline" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -345,10 +333,7 @@ "type": "state-timeline" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -453,10 +438,7 @@ "type": "state-timeline" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -574,10 +556,7 @@ "type": "state-timeline" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -641,10 +620,7 @@ "type": "state-timeline" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-timeseries/timeseries-formats.json b/devenv/dev-dashboards/panel-timeseries/timeseries-formats.json index 5fb3feec5d8..9740e74a16b 100644 --- a/devenv/dev-dashboards/panel-timeseries/timeseries-formats.json +++ b/devenv/dev-dashboards/panel-timeseries/timeseries-formats.json @@ -803,10 +803,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 8, "w": 5, @@ -828,10 +825,7 @@ "type": "text" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-timeseries/timeseries-nulls.json b/devenv/dev-dashboards/panel-timeseries/timeseries-nulls.json index f2ee29ebf5e..a107a35423f 100644 --- a/devenv/dev-dashboards/panel-timeseries/timeseries-nulls.json +++ b/devenv/dev-dashboards/panel-timeseries/timeseries-nulls.json @@ -1385,10 +1385,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -1476,10 +1473,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -1571,10 +1565,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -1664,10 +1655,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -1757,10 +1745,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -1850,10 +1835,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -1943,10 +1925,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -2036,10 +2015,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -2237,10 +2213,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -2332,10 +2305,7 @@ "type": "trend" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-timeseries/timeseries-stacking2.json b/devenv/dev-dashboards/panel-timeseries/timeseries-stacking2.json index 1c12a9a2982..18588cd8830 100644 --- a/devenv/dev-dashboards/panel-timeseries/timeseries-stacking2.json +++ b/devenv/dev-dashboards/panel-timeseries/timeseries-stacking2.json @@ -28,10 +28,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "description": "", "fieldConfig": { "defaults": { @@ -308,10 +305,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "description": "", "fieldConfig": { "defaults": { @@ -612,10 +606,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "description": "", "fieldConfig": { "defaults": { @@ -942,10 +933,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "description": "", "fieldConfig": { "defaults": { @@ -1498,10 +1486,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -1848,10 +1833,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "description": "", "fieldConfig": { "defaults": { @@ -2065,10 +2047,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "description": "", "fieldConfig": { "defaults": { @@ -2177,10 +2156,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -2498,10 +2474,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -2634,10 +2607,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -2770,10 +2740,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -2906,10 +2873,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -3145,10 +3109,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -3384,10 +3345,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-timeseries/timeseries-yaxis-ticks.json b/devenv/dev-dashboards/panel-timeseries/timeseries-yaxis-ticks.json index d7b2e36c4ba..5cf44cae03f 100644 --- a/devenv/dev-dashboards/panel-timeseries/timeseries-yaxis-ticks.json +++ b/devenv/dev-dashboards/panel-timeseries/timeseries-yaxis-ticks.json @@ -39,10 +39,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -133,10 +130,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -227,10 +221,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -321,10 +312,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -415,10 +403,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -509,10 +494,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -603,10 +585,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "description": "", "fieldConfig": { "defaults": { @@ -698,10 +677,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "description": "", "fieldConfig": { "defaults": { @@ -793,10 +769,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -887,10 +860,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -977,10 +947,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -1067,10 +1034,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "description": "", "fieldConfig": { "defaults": { @@ -1158,10 +1122,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -1250,10 +1211,7 @@ "type": "timeseries" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-trend/trend_example.json b/devenv/dev-dashboards/panel-trend/trend_example.json index a64547cec0b..72d9b9b7566 100644 --- a/devenv/dev-dashboards/panel-trend/trend_example.json +++ b/devenv/dev-dashboards/panel-trend/trend_example.json @@ -22,10 +22,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-xychart/xychart-migrations.json b/devenv/dev-dashboards/panel-xychart/xychart-migrations.json index 332eaf8faab..326c32087b9 100644 --- a/devenv/dev-dashboards/panel-xychart/xychart-migrations.json +++ b/devenv/dev-dashboards/panel-xychart/xychart-migrations.json @@ -28,10 +28,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -111,10 +108,7 @@ "type": "xychart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -261,10 +255,7 @@ "type": "xychart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -459,10 +450,7 @@ "type": "xychart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "gridPos": { "h": 7, "w": 3, @@ -483,10 +471,7 @@ "type": "text" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -584,10 +569,7 @@ "type": "xychart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -741,10 +723,7 @@ "type": "xychart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -1832,10 +1811,7 @@ "type": "xychart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -1933,10 +1909,7 @@ "type": "xychart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -2034,10 +2007,7 @@ "type": "xychart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/panel-xychart/xychart-tooltip-color-test.json b/devenv/dev-dashboards/panel-xychart/xychart-tooltip-color-test.json index 5ee05b7b7f9..e92699d02e3 100644 --- a/devenv/dev-dashboards/panel-xychart/xychart-tooltip-color-test.json +++ b/devenv/dev-dashboards/panel-xychart/xychart-tooltip-color-test.json @@ -577,10 +577,7 @@ "type": "xychart" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/transforms/join-by-field.json b/devenv/dev-dashboards/transforms/join-by-field.json index 9bdd8aabd2c..2c254fc929e 100644 --- a/devenv/dev-dashboards/transforms/join-by-field.json +++ b/devenv/dev-dashboards/transforms/join-by-field.json @@ -42,10 +42,7 @@ "type": "row" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { @@ -274,10 +271,7 @@ "type": "row" }, { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { diff --git a/devenv/dev-dashboards/transforms/join-by-labels.json b/devenv/dev-dashboards/transforms/join-by-labels.json index 823a91a23e1..7b546b58c80 100644 --- a/devenv/dev-dashboards/transforms/join-by-labels.json +++ b/devenv/dev-dashboards/transforms/join-by-labels.json @@ -29,10 +29,7 @@ "liveNow": false, "panels": [ { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, + "datasource": { "type": "testdata" }, "fieldConfig": { "defaults": { "color": { From 8cee546d678668b2622b54325dd7df9484e6c403 Mon Sep 17 00:00:00 2001 From: Ihor Yeromin Date: Wed, 23 Apr 2025 12:27:09 +0200 Subject: [PATCH 041/146] DataLinks: Long link title wrapping (#104169) chore(data-links): link text wrap --- .../src/components/VizTooltip/VizTooltipFooter.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.tsx b/packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.tsx index 5b79c42711f..2552e1e6541 100644 --- a/packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.tsx +++ b/packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.tsx @@ -117,6 +117,12 @@ const getStyles = (theme: GrafanaTheme2) => ({ textDecoration: 'underline', background: 'none', }, + + height: 'auto', + '& span': { + whiteSpace: 'normal', + textAlign: 'left', + }, }), oneClickWrapper: css({ display: 'flex', From 3923538ba4fb7442422203780f612ff8a8da2ac0 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 23 Apr 2025 13:30:30 +0300 Subject: [PATCH 042/146] Provisioning: Delete from repository, not the resource client on rename (#104291) Co-authored-by: Roberto Jimenez Sanchez --- .../apis/provisioning/jobs/export/all.go | 9 +- .../apis/provisioning/jobs/export/folders.go | 6 +- .../jobs/export/mock_export_fn.go | 23 +- .../apis/provisioning/jobs/export/worker.go | 10 +- .../provisioning/jobs/export/worker_test.go | 38 -- .../jobs/migrate/legacy_folders.go | 98 ------ .../jobs/migrate/legacy_folders_test.go | 326 ------------------ .../jobs/migrate/legacy_resources.go | 33 +- .../jobs/migrate/legacy_resources_test.go | 277 ++++++++++----- .../migrate/mock_legacy_folders_migrator.go | 240 ------------- pkg/registry/apis/provisioning/register.go | 4 +- .../provisioning/repository/go-git/wrapper.go | 19 +- 12 files changed, 263 insertions(+), 820 deletions(-) delete mode 100644 pkg/registry/apis/provisioning/jobs/migrate/legacy_folders.go delete mode 100644 pkg/registry/apis/provisioning/jobs/migrate/legacy_folders_test.go delete mode 100644 pkg/registry/apis/provisioning/jobs/migrate/mock_legacy_folders_migrator.go diff --git a/pkg/registry/apis/provisioning/jobs/export/all.go b/pkg/registry/apis/provisioning/jobs/export/all.go index 2f4b2253f76..1010c02f630 100644 --- a/pkg/registry/apis/provisioning/jobs/export/all.go +++ b/pkg/registry/apis/provisioning/jobs/export/all.go @@ -6,11 +6,16 @@ import ( provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs" "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" - "k8s.io/client-go/dynamic" ) -func ExportAll(ctx context.Context, repoName string, options provisioning.ExportJobOptions, clients resources.ResourceClients, repositoryResources resources.RepositoryResources, folderClient dynamic.ResourceInterface, progress jobs.JobProgressRecorder) error { +func ExportAll(ctx context.Context, repoName string, options provisioning.ExportJobOptions, clients resources.ResourceClients, repositoryResources resources.RepositoryResources, progress jobs.JobProgressRecorder) error { // FIXME: should we sign with grafana user? + + folderClient, err := clients.Folder() + if err != nil { + return err + } + if err := ExportFolders(ctx, repoName, options, folderClient, repositoryResources, progress); err != nil { return err } diff --git a/pkg/registry/apis/provisioning/jobs/export/folders.go b/pkg/registry/apis/provisioning/jobs/export/folders.go index 954ff5dc358..afc7db05a87 100644 --- a/pkg/registry/apis/provisioning/jobs/export/folders.go +++ b/pkg/registry/apis/provisioning/jobs/export/folders.go @@ -5,15 +5,17 @@ import ( "errors" "fmt" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/client-go/dynamic" + "github.com/grafana/grafana/pkg/apimachinery/utils" provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs" "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository" "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/client-go/dynamic" ) +// ExportFolders will load the full folder tree into memory and update the repositoryResources tree func ExportFolders(ctx context.Context, repoName string, options provisioning.ExportJobOptions, folderClient dynamic.ResourceInterface, repositoryResources resources.RepositoryResources, progress jobs.JobProgressRecorder) error { // Load and write all folders // FIXME: we load the entire tree in memory diff --git a/pkg/registry/apis/provisioning/jobs/export/mock_export_fn.go b/pkg/registry/apis/provisioning/jobs/export/mock_export_fn.go index 7e7a405055a..e861e7da3c3 100644 --- a/pkg/registry/apis/provisioning/jobs/export/mock_export_fn.go +++ b/pkg/registry/apis/provisioning/jobs/export/mock_export_fn.go @@ -6,8 +6,6 @@ import ( context "context" jobs "github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs" - dynamic "k8s.io/client-go/dynamic" - mock "github.com/stretchr/testify/mock" resources "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" @@ -28,17 +26,17 @@ func (_m *MockExportFn) EXPECT() *MockExportFn_Expecter { return &MockExportFn_Expecter{mock: &_m.Mock} } -// Execute provides a mock function with given fields: ctx, repoName, options, clients, repositoryResources, folderClient, progress -func (_m *MockExportFn) Execute(ctx context.Context, repoName string, options v0alpha1.ExportJobOptions, clients resources.ResourceClients, repositoryResources resources.RepositoryResources, folderClient dynamic.ResourceInterface, progress jobs.JobProgressRecorder) error { - ret := _m.Called(ctx, repoName, options, clients, repositoryResources, folderClient, progress) +// Execute provides a mock function with given fields: ctx, repoName, options, clients, repositoryResources, progress +func (_m *MockExportFn) Execute(ctx context.Context, repoName string, options v0alpha1.ExportJobOptions, clients resources.ResourceClients, repositoryResources resources.RepositoryResources, progress jobs.JobProgressRecorder) error { + ret := _m.Called(ctx, repoName, options, clients, repositoryResources, progress) if len(ret) == 0 { panic("no return value specified for Execute") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, v0alpha1.ExportJobOptions, resources.ResourceClients, resources.RepositoryResources, dynamic.ResourceInterface, jobs.JobProgressRecorder) error); ok { - r0 = rf(ctx, repoName, options, clients, repositoryResources, folderClient, progress) + if rf, ok := ret.Get(0).(func(context.Context, string, v0alpha1.ExportJobOptions, resources.ResourceClients, resources.RepositoryResources, jobs.JobProgressRecorder) error); ok { + r0 = rf(ctx, repoName, options, clients, repositoryResources, progress) } else { r0 = ret.Error(0) } @@ -57,15 +55,14 @@ type MockExportFn_Execute_Call struct { // - options v0alpha1.ExportJobOptions // - clients resources.ResourceClients // - repositoryResources resources.RepositoryResources -// - folderClient dynamic.ResourceInterface // - progress jobs.JobProgressRecorder -func (_e *MockExportFn_Expecter) Execute(ctx interface{}, repoName interface{}, options interface{}, clients interface{}, repositoryResources interface{}, folderClient interface{}, progress interface{}) *MockExportFn_Execute_Call { - return &MockExportFn_Execute_Call{Call: _e.mock.On("Execute", ctx, repoName, options, clients, repositoryResources, folderClient, progress)} +func (_e *MockExportFn_Expecter) Execute(ctx interface{}, repoName interface{}, options interface{}, clients interface{}, repositoryResources interface{}, progress interface{}) *MockExportFn_Execute_Call { + return &MockExportFn_Execute_Call{Call: _e.mock.On("Execute", ctx, repoName, options, clients, repositoryResources, progress)} } -func (_c *MockExportFn_Execute_Call) Run(run func(ctx context.Context, repoName string, options v0alpha1.ExportJobOptions, clients resources.ResourceClients, repositoryResources resources.RepositoryResources, folderClient dynamic.ResourceInterface, progress jobs.JobProgressRecorder)) *MockExportFn_Execute_Call { +func (_c *MockExportFn_Execute_Call) Run(run func(ctx context.Context, repoName string, options v0alpha1.ExportJobOptions, clients resources.ResourceClients, repositoryResources resources.RepositoryResources, progress jobs.JobProgressRecorder)) *MockExportFn_Execute_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(v0alpha1.ExportJobOptions), args[3].(resources.ResourceClients), args[4].(resources.RepositoryResources), args[5].(dynamic.ResourceInterface), args[6].(jobs.JobProgressRecorder)) + run(args[0].(context.Context), args[1].(string), args[2].(v0alpha1.ExportJobOptions), args[3].(resources.ResourceClients), args[4].(resources.RepositoryResources), args[5].(jobs.JobProgressRecorder)) }) return _c } @@ -75,7 +72,7 @@ func (_c *MockExportFn_Execute_Call) Return(_a0 error) *MockExportFn_Execute_Cal return _c } -func (_c *MockExportFn_Execute_Call) RunAndReturn(run func(context.Context, string, v0alpha1.ExportJobOptions, resources.ResourceClients, resources.RepositoryResources, dynamic.ResourceInterface, jobs.JobProgressRecorder) error) *MockExportFn_Execute_Call { +func (_c *MockExportFn_Execute_Call) RunAndReturn(run func(context.Context, string, v0alpha1.ExportJobOptions, resources.ResourceClients, resources.RepositoryResources, jobs.JobProgressRecorder) error) *MockExportFn_Execute_Call { _c.Call.Return(run) return _c } diff --git a/pkg/registry/apis/provisioning/jobs/export/worker.go b/pkg/registry/apis/provisioning/jobs/export/worker.go index 73e4eaa6c2e..c8347746584 100644 --- a/pkg/registry/apis/provisioning/jobs/export/worker.go +++ b/pkg/registry/apis/provisioning/jobs/export/worker.go @@ -11,11 +11,10 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs" "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository" "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" - "k8s.io/client-go/dynamic" ) //go:generate mockery --name ExportFn --structname MockExportFn --inpackage --filename mock_export_fn.go --with-expecter -type ExportFn func(ctx context.Context, repoName string, options provisioning.ExportJobOptions, clients resources.ResourceClients, repositoryResources resources.RepositoryResources, folderClient dynamic.ResourceInterface, progress jobs.JobProgressRecorder) error +type ExportFn func(ctx context.Context, repoName string, options provisioning.ExportJobOptions, clients resources.ResourceClients, repositoryResources resources.RepositoryResources, progress jobs.JobProgressRecorder) error //go:generate mockery --name WrapWithCloneFn --structname MockWrapWithCloneFn --inpackage --filename mock_wrap_with_clone_fn.go --with-expecter type WrapWithCloneFn func(ctx context.Context, repo repository.Repository, cloneOptions repository.CloneOptions, pushOptions repository.PushOptions, fn func(repo repository.Repository, cloned bool) error) error @@ -87,11 +86,6 @@ func (r *ExportWorker) Process(ctx context.Context, repo repository.Repository, return fmt.Errorf("create clients: %w", err) } - folderClient, err := clients.Folder() - if err != nil { - return fmt.Errorf("create folder client: %w", err) - } - rw, ok := repo.(repository.ReaderWriter) if !ok { return errors.New("export job submitted targeting repository that is not a ReaderWriter") @@ -102,7 +96,7 @@ func (r *ExportWorker) Process(ctx context.Context, repo repository.Repository, return fmt.Errorf("create repository resource client: %w", err) } - return r.exportFn(ctx, cfg.Name, *options, clients, repositoryResources, folderClient, progress) + return r.exportFn(ctx, cfg.Name, *options, clients, repositoryResources, progress) } return r.wrapWithCloneFn(ctx, repo, cloneOptions, pushOptions, fn) diff --git a/pkg/registry/apis/provisioning/jobs/export/worker_test.go b/pkg/registry/apis/provisioning/jobs/export/worker_test.go index 3e56f63047f..7a2bfaf0c4f 100644 --- a/pkg/registry/apis/provisioning/jobs/export/worker_test.go +++ b/pkg/registry/apis/provisioning/jobs/export/worker_test.go @@ -177,7 +177,6 @@ func TestExportWorker_ProcessNotReaderWriter(t *testing.T) { resourceClients := resources.NewMockResourceClients(t) mockClients := resources.NewMockClientFactory(t) mockClients.On("Clients", context.Background(), "test-namespace").Return(resourceClients, nil) - resourceClients.On("Folder").Return(nil, nil) mockProgress := jobs.NewMockJobProgressRecorder(t) mockCloneFn := NewMockWrapWithCloneFn(t) @@ -190,40 +189,6 @@ func TestExportWorker_ProcessNotReaderWriter(t *testing.T) { require.EqualError(t, err, "export job submitted targeting repository that is not a ReaderWriter") } -func TestExportWorker_ProcessFolderClientError(t *testing.T) { - job := v0alpha1.Job{ - Spec: v0alpha1.JobSpec{ - Action: v0alpha1.JobActionPush, - Push: &v0alpha1.ExportJobOptions{}, - }, - } - - mockRepo := repository.NewMockRepository(t) - mockRepo.On("Config").Return(&v0alpha1.Repository{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-repo", - Namespace: "test-namespace", - }, - Spec: v0alpha1.RepositorySpec{ - Workflows: []v0alpha1.Workflow{v0alpha1.WriteWorkflow}, - }, - }) - - resourceClients := resources.NewMockResourceClients(t) - mockClients := resources.NewMockClientFactory(t) - mockClients.On("Clients", context.Background(), "test-namespace").Return(resourceClients, nil) - resourceClients.On("Folder").Return(nil, fmt.Errorf("failed to create folder client")) - - mockProgress := jobs.NewMockJobProgressRecorder(t) - mockCloneFn := NewMockWrapWithCloneFn(t) - mockCloneFn.On("Execute", context.Background(), mockRepo, mock.Anything, mock.Anything, mock.Anything).Return(func(ctx context.Context, repo repository.Repository, cloneOpts repository.CloneOptions, pushOpts repository.PushOptions, fn func(repository.Repository, bool) error) error { - return fn(repo, true) - }) - r := NewExportWorker(mockClients, nil, nil, mockCloneFn.Execute) - err := r.Process(context.Background(), mockRepo, job, mockProgress) - require.EqualError(t, err, "create folder client: failed to create folder client") -} - func TestExportWorker_ProcessRepositoryResourcesError(t *testing.T) { job := v0alpha1.Job{ Spec: v0alpha1.JobSpec{ @@ -244,7 +209,6 @@ func TestExportWorker_ProcessRepositoryResourcesError(t *testing.T) { }) resourceClients := resources.NewMockResourceClients(t) - resourceClients.On("Folder").Return(nil, nil) mockClients := resources.NewMockClientFactory(t) mockClients.On("Clients", context.Background(), "test-namespace").Return(resourceClients, nil) @@ -288,7 +252,6 @@ func TestExportWorker_ProcessCloneAndPushOptions(t *testing.T) { mockClients := resources.NewMockClientFactory(t) mockResourceClients := resources.NewMockResourceClients(t) mockClients.On("Clients", mock.Anything, "test-namespace").Return(mockResourceClients, nil) - mockResourceClients.On("Folder").Return(nil, nil) mockRepoResources := resources.NewMockRepositoryResourcesFactory(t) mockRepoResourcesClient := resources.NewMockRepositoryResources(t) @@ -339,7 +302,6 @@ func TestExportWorker_ProcessExportFnError(t *testing.T) { mockClients := resources.NewMockClientFactory(t) mockResourceClients := resources.NewMockResourceClients(t) mockClients.On("Clients", mock.Anything, "test-namespace").Return(mockResourceClients, nil) - mockResourceClients.On("Folder").Return(nil, nil) mockRepoResources := resources.NewMockRepositoryResourcesFactory(t) mockRepoResourcesClient := resources.NewMockRepositoryResources(t) diff --git a/pkg/registry/apis/provisioning/jobs/migrate/legacy_folders.go b/pkg/registry/apis/provisioning/jobs/migrate/legacy_folders.go deleted file mode 100644 index 6f58eff634b..00000000000 --- a/pkg/registry/apis/provisioning/jobs/migrate/legacy_folders.go +++ /dev/null @@ -1,98 +0,0 @@ -package migrate - -import ( - "context" - "errors" - "fmt" - - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" - - "github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy" - "github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs" - "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository" - "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" - "github.com/grafana/grafana/pkg/storage/unified/parquet" - "github.com/grafana/grafana/pkg/storage/unified/resource" -) - -const maxFolders = 10000 - -//go:generate mockery --name LegacyFoldersMigrator --structname MockLegacyFoldersMigrator --inpackage --filename mock_legacy_folders_migrator.go --with-expecter -type LegacyFoldersMigrator interface { - resource.BulkResourceWriter - Migrate(ctx context.Context, namespace string, repositoryResources resources.RepositoryResources, progress jobs.JobProgressRecorder) error -} - -type legacyFoldersMigrator struct { - tree resources.FolderTree - legacyMigrator legacy.LegacyMigrator -} - -func NewLegacyFoldersMigrator(legacyMigrator legacy.LegacyMigrator) LegacyFoldersMigrator { - return &legacyFoldersMigrator{ - legacyMigrator: legacyMigrator, - tree: resources.NewEmptyFolderTree(), - } -} - -// Close implements resource.BulkResourceWrite. -func (f *legacyFoldersMigrator) Close() error { - return nil -} - -// CloseWithResults implements resource.BulkResourceWrite. -func (f *legacyFoldersMigrator) CloseWithResults() (*resource.BulkResponse, error) { - return &resource.BulkResponse{}, nil -} - -// Write implements resource.BulkResourceWrite. -func (f *legacyFoldersMigrator) Write(ctx context.Context, key *resource.ResourceKey, value []byte) error { - item := &unstructured.Unstructured{} - err := item.UnmarshalJSON(value) - if err != nil { - return fmt.Errorf("unmarshal unstructured to JSON: %w", err) - } - - if f.tree.Count() > maxFolders { - return errors.New("too many folders") - } - - // TODO: should we check if managed already and abort migration? - - return f.tree.AddUnstructured(item) -} - -func (f *legacyFoldersMigrator) Migrate(ctx context.Context, namespace string, repositoryResources resources.RepositoryResources, progress jobs.JobProgressRecorder) error { - progress.SetMessage(ctx, "read folders from SQL") - if _, err := f.legacyMigrator.Migrate(ctx, legacy.MigrateOptions{ - Namespace: namespace, - Resources: []schema.GroupResource{resources.FolderResource.GroupResource()}, - Store: parquet.NewBulkResourceWriterClient(f), - }); err != nil { - return fmt.Errorf("read folders from SQL: %w", err) - } - - progress.SetMessage(ctx, "export folders from SQL") - // FIXME: we don't sign folders, not even with grafana user - if err := repositoryResources.EnsureFolderTreeExists(ctx, "", "", f.tree, func(folder resources.Folder, created bool, err error) error { - result := jobs.JobResourceResult{ - Action: repository.FileActionCreated, - Name: folder.ID, - Resource: resources.FolderResource.Resource, - Group: resources.FolderResource.Group, - Path: folder.Path, - Error: err, - } - - if !created { - result.Action = repository.FileActionIgnored - } - progress.Record(ctx, result) - return err - }); err != nil { - return fmt.Errorf("export folders from SQL: %w", err) - } - - return nil -} diff --git a/pkg/registry/apis/provisioning/jobs/migrate/legacy_folders_test.go b/pkg/registry/apis/provisioning/jobs/migrate/legacy_folders_test.go deleted file mode 100644 index c5c11850638..00000000000 --- a/pkg/registry/apis/provisioning/jobs/migrate/legacy_folders_test.go +++ /dev/null @@ -1,326 +0,0 @@ -package migrate - -import ( - "context" - "errors" - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - - "github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy" - "github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs" - "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository" - "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" - "github.com/grafana/grafana/pkg/storage/unified/resource" -) - -func TestLegacyFoldersMigrator_Write(t *testing.T) { - t.Run("should fail when json is invalid", func(t *testing.T) { - migrator := NewLegacyFoldersMigrator(legacy.NewMockLegacyMigrator(t)) - err := migrator.Write(context.Background(), nil, []byte("invalid json")) - require.Error(t, err) - require.Contains(t, err.Error(), "unmarshal unstructured to JSON") - }) - - t.Run("should fail when too many folders", func(t *testing.T) { - migrator := NewLegacyFoldersMigrator(legacy.NewMockLegacyMigrator(t)) - - // Write more than maxFolders - for i := 0; i <= maxFolders+1; i++ { - folder := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "folder.grafana.app/v1alpha1", - "kind": "Folder", - "metadata": map[string]interface{}{ - "name": fmt.Sprintf("test-folder-%d", i), - }, - }, - } - folder.SetKind("Folder") - folder.SetAPIVersion("folder.grafana.app/v1alpha1") - - data, err := folder.MarshalJSON() - require.NoError(t, err) - if i == maxFolders+1 { - err = migrator.Write(context.Background(), nil, data) - require.Error(t, err) - require.Equal(t, "too many folders", err.Error()) - return - } - err = migrator.Write(context.Background(), nil, data) - require.NoError(t, err) - } - }) - - t.Run("should add folder to tree", func(t *testing.T) { - migrator := NewLegacyFoldersMigrator(legacy.NewMockLegacyMigrator(t)) - folder := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "folder.grafana.app/v1alpha1", - "kind": "Folder", - "metadata": map[string]interface{}{ - "name": "test-folder", - "annotations": map[string]interface{}{ - "folder.grafana.app/uid": "test-folder-uid", - }, - }, - }, - } - folder.SetKind("Folder") - folder.SetAPIVersion("folder.grafana.app/v1alpha1") - - data, err := folder.MarshalJSON() - require.NoError(t, err) - - err = migrator.Write(context.Background(), nil, data) - require.NoError(t, err) - }) -} - -func TestLegacyFoldersMigrator_Migrate(t *testing.T) { - t.Run("should fail when legacy migrator fails", func(t *testing.T) { - mockLegacyMigrator := legacy.NewMockLegacyMigrator(t) - mockLegacyMigrator.On("Migrate", mock.Anything, mock.MatchedBy(func(opts legacy.MigrateOptions) bool { - return opts.Namespace == "test-namespace" && - len(opts.Resources) == 1 && - opts.Resources[0] == resources.FolderResource.GroupResource() - }), mock.Anything).Return(nil, errors.New("migration failed")) - - migrator := NewLegacyFoldersMigrator(mockLegacyMigrator) - progress := jobs.NewMockJobProgressRecorder(t) - progress.On("SetMessage", mock.Anything, "read folders from SQL").Return() - - err := migrator.Migrate(context.Background(), "test-namespace", nil, progress) - require.Error(t, err) - require.Contains(t, err.Error(), "read folders from SQL: migration failed") - progress.AssertExpectations(t) - }) - - t.Run("should fail when folder tree creation fails", func(t *testing.T) { - mockLegacyMigrator := legacy.NewMockLegacyMigrator(t) - mockLegacyMigrator.On("Migrate", mock.Anything, mock.MatchedBy(func(opts legacy.MigrateOptions) bool { - return opts.Namespace == "test-namespace" && - len(opts.Resources) == 1 && - opts.Resources[0] == resources.FolderResource.GroupResource() - }), mock.Anything).Return(&resource.BulkResponse{}, nil) - - mockRepositoryResources := resources.NewMockRepositoryResources(t) - mockRepositoryResources.On("EnsureFolderTreeExists", mock.Anything, "", "", mock.Anything, mock.Anything). - Return(errors.New("folder tree creation failed")) - - migrator := NewLegacyFoldersMigrator(mockLegacyMigrator) - progress := jobs.NewMockJobProgressRecorder(t) - progress.On("SetMessage", mock.Anything, "read folders from SQL").Return() - progress.On("SetMessage", mock.Anything, "export folders from SQL").Return() - - err := migrator.Migrate(context.Background(), "test-namespace", mockRepositoryResources, progress) - require.Error(t, err) - require.Contains(t, err.Error(), "export folders from SQL: folder tree creation failed") - - progress.AssertExpectations(t) - }) - - t.Run("should successfully migrate folders", func(t *testing.T) { - mockLegacyMigrator := legacy.NewMockLegacyMigrator(t) - mockLegacyMigrator.On("Migrate", mock.Anything, mock.MatchedBy(func(opts legacy.MigrateOptions) bool { - return opts.Namespace == "test-namespace" && - len(opts.Resources) == 1 && - opts.Resources[0] == resources.FolderResource.GroupResource() - }), mock.Anything).Run(func(args mock.Arguments) { - // Simulate writing a folder through the bulk writer - opts := args.Get(1).(legacy.MigrateOptions) - folder := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "folder.grafana.app/v1alpha1", - "kind": "Folder", - "metadata": map[string]interface{}{ - "name": "test-folder", - "annotations": map[string]interface{}{ - "folder.grafana.app/uid": "test-folder-uid", - }, - }, - }, - } - folder.SetKind("Folder") - folder.SetAPIVersion("folder.grafana.app/v1alpha1") - - data, err := folder.MarshalJSON() - require.NoError(t, err) - client, err := opts.Store.BulkProcess(context.Background()) - require.NoError(t, err) - require.NoError(t, client.Send(&resource.BulkRequest{ - Key: &resource.ResourceKey{Namespace: "test-namespace", Name: "test-folder"}, - Value: data, - })) - }).Return(&resource.BulkResponse{}, nil) - - mockRepositoryResources := resources.NewMockRepositoryResources(t) - mockRepositoryResources.On("EnsureFolderTreeExists", mock.Anything, "", "", mock.Anything, mock.Anything). - Run(func(args mock.Arguments) { - callback := args.Get(4).(func(folder resources.Folder, created bool, err error) error) - err := callback(resources.Folder{ - ID: "test-folder-uid", - Path: "/test-folder", - }, true, nil) - require.NoError(t, err) - }).Return(nil) - - migrator := NewLegacyFoldersMigrator(mockLegacyMigrator) - progress := jobs.NewMockJobProgressRecorder(t) - progress.On("SetMessage", mock.Anything, "read folders from SQL").Return() - progress.On("SetMessage", mock.Anything, "export folders from SQL").Return() - progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { - return result.Action == repository.FileActionCreated && - result.Name == "test-folder-uid" && - result.Resource == resources.FolderResource.Resource && - result.Group == resources.FolderResource.Group && - result.Path == "/test-folder" && - result.Error == nil - })).Return() - - err := migrator.Migrate(context.Background(), "test-namespace", mockRepositoryResources, progress) - require.NoError(t, err) - progress.AssertExpectations(t) - }) - t.Run("should ignore folders that already exist", func(t *testing.T) { - mockLegacyMigrator := legacy.NewMockLegacyMigrator(t) - mockLegacyMigrator.On("Migrate", mock.Anything, mock.MatchedBy(func(opts legacy.MigrateOptions) bool { - return opts.Namespace == "test-namespace" && - len(opts.Resources) == 1 && - opts.Resources[0] == resources.FolderResource.GroupResource() - }), mock.Anything).Run(func(args mock.Arguments) { - // Simulate writing a folder through the bulk writer - opts := args.Get(1).(legacy.MigrateOptions) - folder := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "folder.grafana.app/v1alpha1", - "kind": "Folder", - "metadata": map[string]interface{}{ - "name": "test-folder", - "annotations": map[string]interface{}{ - "folder.grafana.app/uid": "test-folder-uid", - }, - }, - }, - } - folder.SetKind("Folder") - folder.SetAPIVersion("folder.grafana.app/v1alpha1") - - data, err := folder.MarshalJSON() - require.NoError(t, err) - client, err := opts.Store.BulkProcess(context.Background()) - require.NoError(t, err) - require.NoError(t, client.Send(&resource.BulkRequest{ - Key: &resource.ResourceKey{Namespace: "test-namespace", Name: "test-folder"}, - Value: data, - })) - }).Return(&resource.BulkResponse{}, nil) - - mockRepositoryResources := resources.NewMockRepositoryResources(t) - mockRepositoryResources.On("EnsureFolderTreeExists", mock.Anything, "", "", mock.Anything, mock.Anything). - Run(func(args mock.Arguments) { - callback := args.Get(4).(func(folder resources.Folder, created bool, err error) error) - err := callback(resources.Folder{ - ID: "test-folder-uid", - Path: "/test-folder", - }, false, nil) - require.NoError(t, err) - }).Return(nil) - - migrator := NewLegacyFoldersMigrator(mockLegacyMigrator) - progress := jobs.NewMockJobProgressRecorder(t) - progress.On("SetMessage", mock.Anything, "read folders from SQL").Return() - progress.On("SetMessage", mock.Anything, "export folders from SQL").Return() - progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { - return result.Action == repository.FileActionIgnored && - result.Name == "test-folder-uid" && - result.Resource == resources.FolderResource.Resource && - result.Group == resources.FolderResource.Group && - result.Path == "/test-folder" && - result.Error == nil - })).Return() - - err := migrator.Migrate(context.Background(), "test-namespace", mockRepositoryResources, progress) - require.NoError(t, err) - progress.AssertExpectations(t) - }) - t.Run("should fail when folder creation fails", func(t *testing.T) { - mockLegacyMigrator := legacy.NewMockLegacyMigrator(t) - mockLegacyMigrator.On("Migrate", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - opts := args.Get(1).(legacy.MigrateOptions) - folder := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "folder.grafana.app/v1alpha1", - "kind": "Folder", - "metadata": map[string]interface{}{ - "name": "test-folder", - "annotations": map[string]interface{}{ - "folder.grafana.app/uid": "test-folder-uid", - }, - }, - }, - } - folder.SetKind("Folder") - folder.SetAPIVersion("folder.grafana.app/v1alpha1") - - data, err := folder.MarshalJSON() - require.NoError(t, err) - client, err := opts.Store.BulkProcess(context.Background()) - require.NoError(t, err) - require.NoError(t, client.Send(&resource.BulkRequest{ - Key: &resource.ResourceKey{Namespace: "test-namespace", Name: "test-folder"}, - Value: data, - })) - }).Return(&resource.BulkResponse{}, nil) - - mockRepositoryResources := resources.NewMockRepositoryResources(t) - expectedError := errors.New("folder creation failed") - mockRepositoryResources.On("EnsureFolderTreeExists", mock.Anything, "", "", mock.Anything, mock.Anything). - Run(func(args mock.Arguments) { - callback := args.Get(4).(func(folder resources.Folder, created bool, err error) error) - // Call the callback with an error and return its result - err := callback(resources.Folder{ - ID: "test-folder-uid", - Path: "/test-folder", - }, true, expectedError) - require.Equal(t, expectedError, err) - }).Return(expectedError) - - migrator := NewLegacyFoldersMigrator(mockLegacyMigrator) - progress := jobs.NewMockJobProgressRecorder(t) - progress.On("SetMessage", mock.Anything, "read folders from SQL").Return() - progress.On("SetMessage", mock.Anything, "export folders from SQL").Return() - progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { - return result.Action == repository.FileActionCreated && - result.Name == "test-folder-uid" && - result.Resource == resources.FolderResource.Resource && - result.Group == resources.FolderResource.Group && - result.Path == "/test-folder" && - assert.Equal(t, expectedError, result.Error) - })).Return() - - err := migrator.Migrate(context.Background(), "test-namespace", mockRepositoryResources, progress) - require.Error(t, err) - require.Contains(t, err.Error(), "export folders from SQL: folder creation failed") - progress.AssertExpectations(t) - }) -} - -func TestLegacyFoldersMigrator_Close(t *testing.T) { - t.Run("should close without error", func(t *testing.T) { - migrator := NewLegacyFoldersMigrator(legacy.NewMockLegacyMigrator(t)) - err := migrator.Close() - require.NoError(t, err) - }) - - t.Run("should close with results without error", func(t *testing.T) { - migrator := NewLegacyFoldersMigrator(legacy.NewMockLegacyMigrator(t)) - resp, err := migrator.CloseWithResults() - require.NoError(t, err) - require.NotNil(t, resp) - }) -} diff --git a/pkg/registry/apis/provisioning/jobs/migrate/legacy_resources.go b/pkg/registry/apis/provisioning/jobs/migrate/legacy_resources.go index 0a34cecb2f7..810d03a245b 100644 --- a/pkg/registry/apis/provisioning/jobs/migrate/legacy_resources.go +++ b/pkg/registry/apis/provisioning/jobs/migrate/legacy_resources.go @@ -10,6 +10,7 @@ import ( provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy" "github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs" + "github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs/export" "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository" "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources/signature" @@ -28,23 +29,26 @@ type legacyResourcesMigrator struct { repositoryResources resources.RepositoryResourcesFactory parsers resources.ParserFactory legacyMigrator legacy.LegacyMigrator - folderMigrator LegacyFoldersMigrator signerFactory signature.SignerFactory + clients resources.ClientFactory + exportFn export.ExportFn } func NewLegacyResourcesMigrator( repositoryResources resources.RepositoryResourcesFactory, parsers resources.ParserFactory, legacyMigrator legacy.LegacyMigrator, - folderMigrator LegacyFoldersMigrator, signerFactory signature.SignerFactory, + clients resources.ClientFactory, + exportFn export.ExportFn, ) LegacyResourcesMigrator { return &legacyResourcesMigrator{ repositoryResources: repositoryResources, parsers: parsers, legacyMigrator: legacyMigrator, - folderMigrator: folderMigrator, signerFactory: signerFactory, + clients: clients, + exportFn: exportFn, } } @@ -70,17 +74,25 @@ func (m *legacyResourcesMigrator) Migrate(ctx context.Context, rw repository.Rea } progress.SetMessage(ctx, "migrate folders from SQL") - if err := m.folderMigrator.Migrate(ctx, namespace, repositoryResources, progress); err != nil { + clients, err := m.clients.Clients(ctx, namespace) + if err != nil { + return err + } + + // nothing special for the export for now + exportOpts := provisioning.ExportJobOptions{} + if err = m.exportFn(ctx, rw.Config().Name, exportOpts, clients, repositoryResources, progress); err != nil { return fmt.Errorf("migrate folders from SQL: %w", err) } progress.SetMessage(ctx, "migrate resources from SQL") for _, kind := range resources.SupportedProvisioningResources { if kind == resources.FolderResource { - continue + continue // folders have special handling } - reader := NewLegacyResourceMigrator( + reader := newLegacyResourceMigrator( + rw, m.legacyMigrator, parser, repositoryResources, @@ -100,6 +112,7 @@ func (m *legacyResourcesMigrator) Migrate(ctx context.Context, rw repository.Rea } type legacyResourceResourceMigrator struct { + repo repository.ReaderWriter legacy legacy.LegacyMigrator parser resources.Parser progress jobs.JobProgressRecorder @@ -111,7 +124,8 @@ type legacyResourceResourceMigrator struct { history map[string]string // UID >> file path } -func NewLegacyResourceMigrator( +func newLegacyResourceMigrator( + repo repository.ReaderWriter, legacy legacy.LegacyMigrator, parser resources.Parser, resources resources.RepositoryResources, @@ -126,6 +140,7 @@ func NewLegacyResourceMigrator( history = make(map[string]string) } return &legacyResourceResourceMigrator{ + repo: repo, legacy: legacy, parser: parser, progress: progress, @@ -178,11 +193,11 @@ func (r *legacyResourceResourceMigrator) Write(ctx context.Context, key *resourc // When replaying history, the path to the file may change over time // This happens when the title or folder change - if r.history != nil { + if r.history != nil && err == nil { name := parsed.Meta.GetName() previous := r.history[name] if previous != "" && previous != fileName { - _, _, err = r.resources.RemoveResourceFromFile(ctx, previous, "") + err = r.repo.Delete(ctx, previous, "", fmt.Sprintf("moved to: %s", fileName)) } r.history[name] = fileName } diff --git a/pkg/registry/apis/provisioning/jobs/migrate/legacy_resources_test.go b/pkg/registry/apis/provisioning/jobs/migrate/legacy_resources_test.go index e73930bd718..50032583024 100644 --- a/pkg/registry/apis/provisioning/jobs/migrate/legacy_resources_test.go +++ b/pkg/registry/apis/provisioning/jobs/migrate/legacy_resources_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" @@ -16,6 +17,7 @@ import ( provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy" "github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs" + "github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs/export" "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository" "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources/signature" @@ -29,13 +31,16 @@ func TestLegacyResourcesMigrator_Migrate(t *testing.T) { Return(nil, errors.New("parser factory error")) signerFactory := signature.NewMockSignerFactory(t) + mockClientFactory := resources.NewMockClientFactory(t) + mockExportFn := export.NewMockExportFn(t) migrator := NewLegacyResourcesMigrator( nil, mockParserFactory, nil, - nil, signerFactory, + mockClientFactory, + mockExportFn.Execute, ) err := migrator.Migrate(context.Background(), nil, "test-namespace", provisioning.MigrateJobOptions{}, jobs.NewMockJobProgressRecorder(t)) @@ -43,6 +48,8 @@ func TestLegacyResourcesMigrator_Migrate(t *testing.T) { require.EqualError(t, err, "get parser: parser factory error") mockParserFactory.AssertExpectations(t) + mockExportFn.AssertExpectations(t) + mockClientFactory.AssertExpectations(t) }) t.Run("should fail when repository resources factory fails", func(t *testing.T) { @@ -51,16 +58,19 @@ func TestLegacyResourcesMigrator_Migrate(t *testing.T) { Return(resources.NewMockParser(t), nil) mockRepoResourcesFactory := resources.NewMockRepositoryResourcesFactory(t) - mockRepoResourcesFactory.On("Client", mock.Anything, mock.Anything, mock.Anything). + mockRepoResourcesFactory.On("Client", mock.Anything, mock.Anything). Return(nil, errors.New("repo resources factory error")) signerFactory := signature.NewMockSignerFactory(t) + mockClientFactory := resources.NewMockClientFactory(t) + mockExportFn := export.NewMockExportFn(t) migrator := NewLegacyResourcesMigrator( mockRepoResourcesFactory, mockParserFactory, nil, - nil, signerFactory, + mockClientFactory, + mockExportFn.Execute, ) err := migrator.Migrate(context.Background(), nil, "test-namespace", provisioning.MigrateJobOptions{}, jobs.NewMockJobProgressRecorder(t)) @@ -69,46 +79,8 @@ func TestLegacyResourcesMigrator_Migrate(t *testing.T) { mockParserFactory.AssertExpectations(t) mockRepoResourcesFactory.AssertExpectations(t) - }) - - t.Run("should fail when folder migrator fails", func(t *testing.T) { - mockParserFactory := resources.NewMockParserFactory(t) - mockParserFactory.On("GetParser", mock.Anything, mock.Anything). - Return(resources.NewMockParser(t), nil) - - mockRepoResources := resources.NewMockRepositoryResources(t) - mockRepoResourcesFactory := resources.NewMockRepositoryResourcesFactory(t) - mockRepoResourcesFactory.On("Client", mock.Anything, mock.Anything, mock.Anything). - Return(mockRepoResources, nil) - - mockFolderMigrator := NewMockLegacyFoldersMigrator(t) - mockFolderMigrator.On("Migrate", mock.Anything, "test-namespace", mockRepoResources, mock.Anything). - Return(errors.New("folder migrator error")) - - progress := jobs.NewMockJobProgressRecorder(t) - progress.On("SetMessage", mock.Anything, mock.Anything).Return() - - signer := signature.NewMockSigner(t) - signerFactory := signature.NewMockSignerFactory(t) - signerFactory.On("New", mock.Anything, mock.Anything). - Return(signer, nil) - - migrator := NewLegacyResourcesMigrator( - mockRepoResourcesFactory, - mockParserFactory, - nil, - mockFolderMigrator, - signerFactory, - ) - - err := migrator.Migrate(context.Background(), nil, "test-namespace", provisioning.MigrateJobOptions{}, progress) - require.Error(t, err) - require.Contains(t, err.Error(), "migrate folders from SQL") - - mockParserFactory.AssertExpectations(t) - mockRepoResourcesFactory.AssertExpectations(t) - mockFolderMigrator.AssertExpectations(t) - progress.AssertExpectations(t) + mockExportFn.AssertExpectations(t) + mockClientFactory.AssertExpectations(t) }) t.Run("should fail when resource migration fails", func(t *testing.T) { @@ -118,13 +90,9 @@ func TestLegacyResourcesMigrator_Migrate(t *testing.T) { mockRepoResources := resources.NewMockRepositoryResources(t) mockRepoResourcesFactory := resources.NewMockRepositoryResourcesFactory(t) - mockRepoResourcesFactory.On("Client", mock.Anything, mock.Anything, mock.Anything). + mockRepoResourcesFactory.On("Client", mock.Anything, mock.Anything). Return(mockRepoResources, nil) - mockFolderMigrator := NewMockLegacyFoldersMigrator(t) - mockFolderMigrator.On("Migrate", mock.Anything, "test-namespace", mockRepoResources, mock.Anything). - Return(nil) - mockLegacyMigrator := legacy.NewMockLegacyMigrator(t) mockLegacyMigrator.On("Migrate", mock.Anything, mock.MatchedBy(func(opts legacy.MigrateOptions) bool { return opts.OnlyCount && opts.Namespace == "test-namespace" @@ -138,23 +106,88 @@ func TestLegacyResourcesMigrator_Migrate(t *testing.T) { signerFactory.On("New", mock.Anything, mock.Anything). Return(signer, nil) + mockClients := resources.NewMockResourceClients(t) + mockClientFactory := resources.NewMockClientFactory(t) + mockClientFactory.On("Clients", mock.Anything, "test-namespace"). + Return(mockClients, nil) + mockExportFn := export.NewMockExportFn(t) + migrator := NewLegacyResourcesMigrator( mockRepoResourcesFactory, mockParserFactory, mockLegacyMigrator, - mockFolderMigrator, signerFactory, + mockClientFactory, + mockExportFn.Execute, ) + repo := repository.NewMockRepository(t) + repo.On("Config").Return(&provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-namespace", + Name: "test-repo", + }, + }) + mockExportFn.On("Execute", mock.Anything, mock.Anything, provisioning.ExportJobOptions{}, mockClients, mockRepoResources, mock.Anything). + Return(nil) - err := migrator.Migrate(context.Background(), nil, "test-namespace", provisioning.MigrateJobOptions{}, progress) + err := migrator.Migrate(context.Background(), repo, "test-namespace", provisioning.MigrateJobOptions{}, progress) require.Error(t, err) require.Contains(t, err.Error(), "migrate resource") mockParserFactory.AssertExpectations(t) mockRepoResourcesFactory.AssertExpectations(t) - mockFolderMigrator.AssertExpectations(t) mockLegacyMigrator.AssertExpectations(t) progress.AssertExpectations(t) + mockExportFn.AssertExpectations(t) + mockClientFactory.AssertExpectations(t) + mockClients.AssertExpectations(t) + repo.AssertExpectations(t) + }) + t.Run("should fail when client creation fails", func(t *testing.T) { + mockParserFactory := resources.NewMockParserFactory(t) + mockParserFactory.On("GetParser", mock.Anything, mock.Anything). + Return(resources.NewMockParser(t), nil) + + mockRepoResources := resources.NewMockRepositoryResources(t) + mockRepoResourcesFactory := resources.NewMockRepositoryResourcesFactory(t) + mockRepoResourcesFactory.On("Client", mock.Anything, mock.Anything). + Return(mockRepoResources, nil) + + mockSigner := signature.NewMockSigner(t) + mockSignerFactory := signature.NewMockSignerFactory(t) + mockSignerFactory.On("New", mock.Anything, mock.Anything). + Return(mockSigner, nil) + + mockClientFactory := resources.NewMockClientFactory(t) + mockClientFactory.On("Clients", mock.Anything, "test-namespace"). + Return(nil, errors.New("client creation error")) + + mockExportFn := export.NewMockExportFn(t) + + progress := jobs.NewMockJobProgressRecorder(t) + progress.On("SetMessage", mock.Anything, "migrate folders from SQL").Return() + + migrator := NewLegacyResourcesMigrator( + mockRepoResourcesFactory, + mockParserFactory, + nil, + mockSignerFactory, + mockClientFactory, + mockExportFn.Execute, + ) + + repo := repository.NewMockRepository(t) + err := migrator.Migrate(context.Background(), repo, "test-namespace", provisioning.MigrateJobOptions{}, progress) + require.Error(t, err) + require.EqualError(t, err, "client creation error") + + mockParserFactory.AssertExpectations(t) + mockRepoResourcesFactory.AssertExpectations(t) + mockSignerFactory.AssertExpectations(t) + mockClientFactory.AssertExpectations(t) + progress.AssertExpectations(t) + mockExportFn.AssertExpectations(t) + repo.AssertExpectations(t) }) t.Run("should fail when signer factory fails", func(t *testing.T) { @@ -164,23 +197,26 @@ func TestLegacyResourcesMigrator_Migrate(t *testing.T) { mockRepoResources := resources.NewMockRepositoryResources(t) mockRepoResourcesFactory := resources.NewMockRepositoryResourcesFactory(t) - mockRepoResourcesFactory.On("Client", mock.Anything, mock.Anything, mock.Anything). + mockRepoResourcesFactory.On("Client", mock.Anything, mock.Anything). Return(mockRepoResources, nil) - mockFolderMigrator := NewMockLegacyFoldersMigrator(t) mockSignerFactory := signature.NewMockSignerFactory(t) mockSignerFactory.On("New", mock.Anything, signature.SignOptions{ Namespace: "test-namespace", History: true, }).Return(nil, fmt.Errorf("signer factory error")) + mockClientFactory := resources.NewMockClientFactory(t) + mockExportFn := export.NewMockExportFn(t) + progress := jobs.NewMockJobProgressRecorder(t) migrator := NewLegacyResourcesMigrator( mockRepoResourcesFactory, mockParserFactory, nil, - mockFolderMigrator, mockSignerFactory, + mockClientFactory, + mockExportFn.Execute, ) err := migrator.Migrate(context.Background(), nil, "test-namespace", provisioning.MigrateJobOptions{ @@ -191,8 +227,66 @@ func TestLegacyResourcesMigrator_Migrate(t *testing.T) { mockParserFactory.AssertExpectations(t) mockRepoResourcesFactory.AssertExpectations(t) - mockFolderMigrator.AssertExpectations(t) mockSignerFactory.AssertExpectations(t) + mockClientFactory.AssertExpectations(t) + progress.AssertExpectations(t) + mockExportFn.AssertExpectations(t) + }) + t.Run("should fail when folder export fails", func(t *testing.T) { + mockParser := resources.NewMockParser(t) + mockParserFactory := resources.NewMockParserFactory(t) + mockParserFactory.On("GetParser", mock.Anything, mock.Anything). + Return(mockParser, nil) + + mockRepoResources := resources.NewMockRepositoryResources(t) + mockRepoResourcesFactory := resources.NewMockRepositoryResourcesFactory(t) + mockRepoResourcesFactory.On("Client", mock.Anything, mock.Anything). + Return(mockRepoResources, nil) + + mockSigner := signature.NewMockSigner(t) + mockSignerFactory := signature.NewMockSignerFactory(t) + mockSignerFactory.On("New", mock.Anything, signature.SignOptions{ + Namespace: "test-namespace", + History: false, + }).Return(mockSigner, nil) + + mockClients := resources.NewMockResourceClients(t) + mockClientFactory := resources.NewMockClientFactory(t) + mockClientFactory.On("Clients", mock.Anything, "test-namespace"). + Return(mockClients, nil) + + mockExportFn := export.NewMockExportFn(t) + mockExportFn.On("Execute", mock.Anything, mock.Anything, provisioning.ExportJobOptions{}, mockClients, mockRepoResources, mock.Anything). + Return(fmt.Errorf("export error")) + + progress := jobs.NewMockJobProgressRecorder(t) + progress.On("SetMessage", mock.Anything, "migrate folders from SQL").Return() + + migrator := NewLegacyResourcesMigrator( + mockRepoResourcesFactory, + mockParserFactory, + nil, + mockSignerFactory, + mockClientFactory, + mockExportFn.Execute, + ) + repo := repository.NewMockRepository(t) + repo.On("Config").Return(&provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-namespace", + Name: "test-repo", + }, + }) + + err := migrator.Migrate(context.Background(), repo, "test-namespace", provisioning.MigrateJobOptions{}, progress) + require.Error(t, err) + require.Contains(t, err.Error(), "migrate folders from SQL: export error") + + mockParserFactory.AssertExpectations(t) + mockRepoResourcesFactory.AssertExpectations(t) + mockSignerFactory.AssertExpectations(t) + mockClientFactory.AssertExpectations(t) + mockExportFn.AssertExpectations(t) progress.AssertExpectations(t) }) @@ -204,13 +298,9 @@ func TestLegacyResourcesMigrator_Migrate(t *testing.T) { mockRepoResources := resources.NewMockRepositoryResources(t) mockRepoResourcesFactory := resources.NewMockRepositoryResourcesFactory(t) - mockRepoResourcesFactory.On("Client", mock.Anything, mock.Anything, mock.Anything). + mockRepoResourcesFactory.On("Client", mock.Anything, mock.Anything). Return(mockRepoResources, nil) - mockFolderMigrator := NewMockLegacyFoldersMigrator(t) - mockFolderMigrator.On("Migrate", mock.Anything, "test-namespace", mockRepoResources, mock.Anything). - Return(nil) - mockSigner := signature.NewMockSigner(t) mockSignerFactory := signature.NewMockSignerFactory(t) mockSignerFactory.On("New", mock.Anything, signature.SignOptions{ @@ -235,6 +325,12 @@ func TestLegacyResourcesMigrator_Migrate(t *testing.T) { }, }, nil).Once() // Migration phase + mockClients := resources.NewMockResourceClients(t) + mockClientFactory := resources.NewMockClientFactory(t) + mockClientFactory.On("Clients", mock.Anything, "test-namespace"). + Return(mockClients, nil) + mockExportFn := export.NewMockExportFn(t) + progress := jobs.NewMockJobProgressRecorder(t) progress.On("SetMessage", mock.Anything, "migrate folders from SQL").Return() progress.On("SetMessage", mock.Anything, "migrate resources from SQL").Return() @@ -244,20 +340,33 @@ func TestLegacyResourcesMigrator_Migrate(t *testing.T) { mockRepoResourcesFactory, mockParserFactory, mockLegacyMigrator, - mockFolderMigrator, mockSignerFactory, + mockClientFactory, + mockExportFn.Execute, ) - err := migrator.Migrate(context.Background(), nil, "test-namespace", provisioning.MigrateJobOptions{ + repo := repository.NewMockRepository(t) + repo.On("Config").Return(&provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-namespace", + Name: "test-repo", + }, + }) + mockExportFn.On("Execute", mock.Anything, mock.Anything, provisioning.ExportJobOptions{}, mockClients, mockRepoResources, mock.Anything). + Return(nil) + + err := migrator.Migrate(context.Background(), repo, "test-namespace", provisioning.MigrateJobOptions{ History: true, }, progress) require.NoError(t, err) mockParserFactory.AssertExpectations(t) mockRepoResourcesFactory.AssertExpectations(t) - mockFolderMigrator.AssertExpectations(t) mockLegacyMigrator.AssertExpectations(t) + mockClientFactory.AssertExpectations(t) + mockExportFn.AssertExpectations(t) progress.AssertExpectations(t) + mockClients.AssertExpectations(t) }) } @@ -269,7 +378,8 @@ func TestLegacyResourceResourceMigrator_Write(t *testing.T) { progress := jobs.NewMockJobProgressRecorder(t) - migrator := NewLegacyResourceMigrator( + migrator := newLegacyResourceMigrator( + nil, nil, mockParser, nil, @@ -318,7 +428,8 @@ func TestLegacyResourceResourceMigrator_Write(t *testing.T) { })).Return() progress.On("TooManyErrors").Return(nil) - migrator := NewLegacyResourceMigrator( + migrator := newLegacyResourceMigrator( + nil, nil, mockParser, mockRepoResources, @@ -360,7 +471,8 @@ func TestLegacyResourceResourceMigrator_Write(t *testing.T) { Return(nil, errors.New("signing error")) progress := jobs.NewMockJobProgressRecorder(t) - migrator := NewLegacyResourceMigrator( + migrator := newLegacyResourceMigrator( + nil, nil, mockParser, nil, @@ -419,7 +531,8 @@ func TestLegacyResourceResourceMigrator_Write(t *testing.T) { })).Return() progress.On("TooManyErrors").Return(nil) - migrator := NewLegacyResourceMigrator( + migrator := newLegacyResourceMigrator( + nil, nil, mockParser, mockRepoResources, @@ -459,10 +572,13 @@ func TestLegacyResourceResourceMigrator_Write(t *testing.T) { Obj: obj, }, nil) + mockRepo := repository.NewMockRepository(t) + mockRepoResources := resources.NewMockRepositoryResources(t) writeResourceFileFromObject := mockRepoResources.On("WriteResourceFileFromObject", mock.Anything, mock.Anything, mock.Anything) - migrator := NewLegacyResourceMigrator( + migrator := newLegacyResourceMigrator( + mockRepo, nil, mockParser, mockRepoResources, @@ -482,8 +598,8 @@ func TestLegacyResourceResourceMigrator_Write(t *testing.T) { // Change the result file name writeResourceFileFromObject.Return("bbbb.json", nil) - mockRepoResources.On("RemoveResourceFromFile", mock.Anything, "aaaa.json", ""). - Return("", schema.GroupVersionKind{}, nil).Once() + mockRepo.On("Delete", mock.Anything, "aaaa.json", "", "moved to: bbbb.json"). + Return(nil).Once() err = migrator.Write(context.Background(), &resource.ResourceKey{}, []byte("")) require.NoError(t, err) @@ -557,7 +673,8 @@ func TestLegacyResourceResourceMigrator_Write(t *testing.T) { })).Return() progress.On("TooManyErrors").Return(nil) - migrator := NewLegacyResourceMigrator( + migrator := newLegacyResourceMigrator( + nil, nil, mockParser, mockRepoResources, @@ -602,7 +719,8 @@ func TestLegacyResourceResourceMigrator_Write(t *testing.T) { progress.On("Record", mock.Anything, mock.Anything).Return() progress.On("TooManyErrors").Return(errors.New("too many errors")) - migrator := NewLegacyResourceMigrator( + migrator := newLegacyResourceMigrator( + nil, nil, mockParser, mockRepoResources, @@ -632,7 +750,8 @@ func TestLegacyResourceResourceMigrator_Migrate(t *testing.T) { progress := jobs.NewMockJobProgressRecorder(t) progress.On("SetMessage", mock.Anything, mock.Anything).Return() - migrator := NewLegacyResourceMigrator( + migrator := newLegacyResourceMigrator( + nil, mockLegacyMigrator, nil, nil, @@ -663,7 +782,8 @@ func TestLegacyResourceResourceMigrator_Migrate(t *testing.T) { progress := jobs.NewMockJobProgressRecorder(t) progress.On("SetMessage", mock.Anything, mock.Anything).Return() - migrator := NewLegacyResourceMigrator( + migrator := newLegacyResourceMigrator( + nil, mockLegacyMigrator, nil, nil, @@ -694,7 +814,8 @@ func TestLegacyResourceResourceMigrator_Migrate(t *testing.T) { progress := jobs.NewMockJobProgressRecorder(t) progress.On("SetMessage", mock.Anything, mock.Anything).Return() - migrator := NewLegacyResourceMigrator( + migrator := newLegacyResourceMigrator( + nil, mockLegacyMigrator, nil, nil, @@ -733,7 +854,8 @@ func TestLegacyResourceResourceMigrator_Migrate(t *testing.T) { progress.On("SetMessage", mock.Anything, mock.Anything).Return() progress.On("SetTotal", mock.Anything, 100).Return() - migrator := NewLegacyResourceMigrator( + migrator := newLegacyResourceMigrator( + nil, mockLegacyMigrator, nil, nil, @@ -773,7 +895,8 @@ func TestLegacyResourceResourceMigrator_Migrate(t *testing.T) { progress.On("SetTotal", mock.Anything, 200).Return() signer := signature.NewMockSigner(t) - migrator := NewLegacyResourceMigrator( + migrator := newLegacyResourceMigrator( + nil, mockLegacyMigrator, nil, nil, diff --git a/pkg/registry/apis/provisioning/jobs/migrate/mock_legacy_folders_migrator.go b/pkg/registry/apis/provisioning/jobs/migrate/mock_legacy_folders_migrator.go deleted file mode 100644 index dca97b2467c..00000000000 --- a/pkg/registry/apis/provisioning/jobs/migrate/mock_legacy_folders_migrator.go +++ /dev/null @@ -1,240 +0,0 @@ -// Code generated by mockery v2.52.4. DO NOT EDIT. - -package migrate - -import ( - context "context" - - jobs "github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs" - mock "github.com/stretchr/testify/mock" - - resource "github.com/grafana/grafana/pkg/storage/unified/resource" - - resources "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" -) - -// MockLegacyFoldersMigrator is an autogenerated mock type for the LegacyFoldersMigrator type -type MockLegacyFoldersMigrator struct { - mock.Mock -} - -type MockLegacyFoldersMigrator_Expecter struct { - mock *mock.Mock -} - -func (_m *MockLegacyFoldersMigrator) EXPECT() *MockLegacyFoldersMigrator_Expecter { - return &MockLegacyFoldersMigrator_Expecter{mock: &_m.Mock} -} - -// Close provides a mock function with no fields -func (_m *MockLegacyFoldersMigrator) Close() error { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for Close") - } - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockLegacyFoldersMigrator_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' -type MockLegacyFoldersMigrator_Close_Call struct { - *mock.Call -} - -// Close is a helper method to define mock.On call -func (_e *MockLegacyFoldersMigrator_Expecter) Close() *MockLegacyFoldersMigrator_Close_Call { - return &MockLegacyFoldersMigrator_Close_Call{Call: _e.mock.On("Close")} -} - -func (_c *MockLegacyFoldersMigrator_Close_Call) Run(run func()) *MockLegacyFoldersMigrator_Close_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *MockLegacyFoldersMigrator_Close_Call) Return(_a0 error) *MockLegacyFoldersMigrator_Close_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockLegacyFoldersMigrator_Close_Call) RunAndReturn(run func() error) *MockLegacyFoldersMigrator_Close_Call { - _c.Call.Return(run) - return _c -} - -// CloseWithResults provides a mock function with no fields -func (_m *MockLegacyFoldersMigrator) CloseWithResults() (*resource.BulkResponse, error) { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for CloseWithResults") - } - - var r0 *resource.BulkResponse - var r1 error - if rf, ok := ret.Get(0).(func() (*resource.BulkResponse, error)); ok { - return rf() - } - if rf, ok := ret.Get(0).(func() *resource.BulkResponse); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*resource.BulkResponse) - } - } - - if rf, ok := ret.Get(1).(func() error); ok { - r1 = rf() - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockLegacyFoldersMigrator_CloseWithResults_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CloseWithResults' -type MockLegacyFoldersMigrator_CloseWithResults_Call struct { - *mock.Call -} - -// CloseWithResults is a helper method to define mock.On call -func (_e *MockLegacyFoldersMigrator_Expecter) CloseWithResults() *MockLegacyFoldersMigrator_CloseWithResults_Call { - return &MockLegacyFoldersMigrator_CloseWithResults_Call{Call: _e.mock.On("CloseWithResults")} -} - -func (_c *MockLegacyFoldersMigrator_CloseWithResults_Call) Run(run func()) *MockLegacyFoldersMigrator_CloseWithResults_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *MockLegacyFoldersMigrator_CloseWithResults_Call) Return(_a0 *resource.BulkResponse, _a1 error) *MockLegacyFoldersMigrator_CloseWithResults_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockLegacyFoldersMigrator_CloseWithResults_Call) RunAndReturn(run func() (*resource.BulkResponse, error)) *MockLegacyFoldersMigrator_CloseWithResults_Call { - _c.Call.Return(run) - return _c -} - -// Migrate provides a mock function with given fields: ctx, namespace, repositoryResources, progress -func (_m *MockLegacyFoldersMigrator) Migrate(ctx context.Context, namespace string, repositoryResources resources.RepositoryResources, progress jobs.JobProgressRecorder) error { - ret := _m.Called(ctx, namespace, repositoryResources, progress) - - if len(ret) == 0 { - panic("no return value specified for Migrate") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, resources.RepositoryResources, jobs.JobProgressRecorder) error); ok { - r0 = rf(ctx, namespace, repositoryResources, progress) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockLegacyFoldersMigrator_Migrate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Migrate' -type MockLegacyFoldersMigrator_Migrate_Call struct { - *mock.Call -} - -// Migrate is a helper method to define mock.On call -// - ctx context.Context -// - namespace string -// - repositoryResources resources.RepositoryResources -// - progress jobs.JobProgressRecorder -func (_e *MockLegacyFoldersMigrator_Expecter) Migrate(ctx interface{}, namespace interface{}, repositoryResources interface{}, progress interface{}) *MockLegacyFoldersMigrator_Migrate_Call { - return &MockLegacyFoldersMigrator_Migrate_Call{Call: _e.mock.On("Migrate", ctx, namespace, repositoryResources, progress)} -} - -func (_c *MockLegacyFoldersMigrator_Migrate_Call) Run(run func(ctx context.Context, namespace string, repositoryResources resources.RepositoryResources, progress jobs.JobProgressRecorder)) *MockLegacyFoldersMigrator_Migrate_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(resources.RepositoryResources), args[3].(jobs.JobProgressRecorder)) - }) - return _c -} - -func (_c *MockLegacyFoldersMigrator_Migrate_Call) Return(_a0 error) *MockLegacyFoldersMigrator_Migrate_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockLegacyFoldersMigrator_Migrate_Call) RunAndReturn(run func(context.Context, string, resources.RepositoryResources, jobs.JobProgressRecorder) error) *MockLegacyFoldersMigrator_Migrate_Call { - _c.Call.Return(run) - return _c -} - -// Write provides a mock function with given fields: ctx, key, value -func (_m *MockLegacyFoldersMigrator) Write(ctx context.Context, key *resource.ResourceKey, value []byte) error { - ret := _m.Called(ctx, key, value) - - if len(ret) == 0 { - panic("no return value specified for Write") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *resource.ResourceKey, []byte) error); ok { - r0 = rf(ctx, key, value) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockLegacyFoldersMigrator_Write_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Write' -type MockLegacyFoldersMigrator_Write_Call struct { - *mock.Call -} - -// Write is a helper method to define mock.On call -// - ctx context.Context -// - key *resource.ResourceKey -// - value []byte -func (_e *MockLegacyFoldersMigrator_Expecter) Write(ctx interface{}, key interface{}, value interface{}) *MockLegacyFoldersMigrator_Write_Call { - return &MockLegacyFoldersMigrator_Write_Call{Call: _e.mock.On("Write", ctx, key, value)} -} - -func (_c *MockLegacyFoldersMigrator_Write_Call) Run(run func(ctx context.Context, key *resource.ResourceKey, value []byte)) *MockLegacyFoldersMigrator_Write_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*resource.ResourceKey), args[2].([]byte)) - }) - return _c -} - -func (_c *MockLegacyFoldersMigrator_Write_Call) Return(_a0 error) *MockLegacyFoldersMigrator_Write_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockLegacyFoldersMigrator_Write_Call) RunAndReturn(run func(context.Context, *resource.ResourceKey, []byte) error) *MockLegacyFoldersMigrator_Write_Call { - _c.Call.Return(run) - return _c -} - -// NewMockLegacyFoldersMigrator creates a new instance of MockLegacyFoldersMigrator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockLegacyFoldersMigrator(t interface { - mock.TestingT - Cleanup(func()) -}) *MockLegacyFoldersMigrator { - mock := &MockLegacyFoldersMigrator{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/registry/apis/provisioning/register.go b/pkg/registry/apis/provisioning/register.go index a65f2008813..a367f5ea2be 100644 --- a/pkg/registry/apis/provisioning/register.go +++ b/pkg/registry/apis/provisioning/register.go @@ -548,13 +548,13 @@ func (b *APIBuilder) GetPostStartHooks() (map[string]genericapiserver.PostStartH syncer, ) signerFactory := signature.NewSignerFactory(b.clients) - legacyFolders := migrate.NewLegacyFoldersMigrator(b.legacyMigrator) legacyResources := migrate.NewLegacyResourcesMigrator( b.repositoryResources, b.parsers, b.legacyMigrator, - legacyFolders, signerFactory, + b.clients, + export.ExportAll, ) storageSwapper := migrate.NewStorageSwapper(b.unified, b.storageStatus) legacyMigrator := migrate.NewLegacyMigrator( diff --git a/pkg/registry/apis/provisioning/repository/go-git/wrapper.go b/pkg/registry/apis/provisioning/repository/go-git/wrapper.go index d4d9af45c23..7a722cee2ce 100644 --- a/pkg/registry/apis/provisioning/repository/go-git/wrapper.go +++ b/pkg/registry/apis/provisioning/repository/go-git/wrapper.go @@ -336,7 +336,10 @@ func (g *GoGitRepo) Write(ctx context.Context, fpath string, ref string, data [] if err != nil { return err } + return g.maybeCommit(ctx, message) +} +func (g *GoGitRepo) maybeCommit(ctx context.Context, message string) error { // Skip commit for each file if !g.opts.PushOnWrites { return nil @@ -351,7 +354,7 @@ func (g *GoGitRepo) Write(ctx context.Context, fpath string, ref string, data [] When: sig.When, } } - _, err = g.tree.Commit(message, opts) + _, err := g.tree.Commit(message, opts) if errors.Is(err, git.ErrEmptyCommit) { return nil // empty commit is fine -- no change } @@ -359,16 +362,22 @@ func (g *GoGitRepo) Write(ctx context.Context, fpath string, ref string, data [] } // Delete implements repository.Repository. -func (g *GoGitRepo) Delete(ctx context.Context, path string, ref string, message string) error { - if _, err := g.tree.Remove(safepath.Join(g.config.Spec.GitHub.Path, path)); err != nil { +func (g *GoGitRepo) Delete(ctx context.Context, fpath string, ref string, message string) error { + fpath = safepath.Join(g.config.Spec.GitHub.Path, fpath) + if err := verifyPathWithoutRef(fpath, ref); err != nil { return err } - - return nil + if _, err := g.tree.Remove(fpath); err != nil { + return err + } + return g.maybeCommit(ctx, message) } // Read implements repository.Repository. func (g *GoGitRepo) Read(ctx context.Context, path string, ref string) (*repository.FileInfo, error) { + if err := verifyPathWithoutRef(path, ref); err != nil { + return nil, err + } readPath := safepath.Join(g.config.Spec.GitHub.Path, path) stat, err := g.tree.Filesystem.Lstat(readPath) if errors.Is(err, fs.ErrNotExist) { From dc0501e3761419a64b755d9d3f8e9a136d7914f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 23 Apr 2025 12:45:28 +0200 Subject: [PATCH 043/146] Alerting: Simplify actions (#103494) --- .../rule-editor/alert-rule-form/AlertRuleForm.tsx | 13 ------------- .../unified/rule-editor/RuleEditorExisting.test.tsx | 6 ++---- public/locales/en-US/grafana.json | 1 - public/test/helpers/alertingRuleEditor.tsx | 1 - 4 files changed, 2 insertions(+), 19 deletions(-) diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx index a1c3dea3c79..5a76be0933f 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx @@ -268,19 +268,6 @@ export const AlertRuleForm = ({ existing, prefill, isManualRestore }: Props) => const actionButtons = ( - {existing && ( - - )}
)} - {shouldAllowSimplifiedRouting ? ( // when simplified routing is enabled and is grafana rule - simplifiedModeInNotificationsStepEnabled ? ( // simplified mode is enabled - - ) : ( - // simplified mode is disabled - - ) - ) : // when simplified routing is not enabled, render the notification preview as we did before - shouldRenderpreview ? ( - - ) : null} + {shouldAllowSimplifiedRouting && simplifiedModeInNotificationsStepEnabled && ( + + )} + {shouldAllowSimplifiedRouting && !simplifiedModeInNotificationsStepEnabled && ( + + )} + {!shouldAllowSimplifiedRouting && shouldRenderpreview && } ); }; diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx index 5a76be0933f..88549aa16f1 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx @@ -362,13 +362,15 @@ export const AlertRuleForm = ({ existing, prefill, isManualRestore }: Props) => onDismiss={() => setShowDeleteModal(false)} /> ) : null} - {showEditYaml ? ( - isGrafanaManagedRuleByType(type) ? ( - setShowEditYaml(false)} /> - ) : ( - setShowEditYaml(false)} /> - ) - ) : null} + {showEditYaml && ( + <> + {isGrafanaManagedRuleByType(type) && ( + setShowEditYaml(false)} /> + )} + + {!isGrafanaManagedRuleByType(type) && setShowEditYaml(false)} />} + + )} ); }; diff --git a/public/app/features/alerting/unified/components/rule-viewer/tabs/version-history/VersionHistoryTable.tsx b/public/app/features/alerting/unified/components/rule-viewer/tabs/version-history/VersionHistoryTable.tsx index e3f9e518e18..c3169462e96 100644 --- a/public/app/features/alerting/unified/components/rule-viewer/tabs/version-history/VersionHistoryTable.tsx +++ b/public/app/features/alerting/unified/components/rule-viewer/tabs/version-history/VersionHistoryTable.tsx @@ -127,9 +127,8 @@ export function VersionHistoryTable({ return ( - {isFirstItem ? ( - - ) : canRestore ? ( + {isFirstItem && } + {!isFirstItem && canRestore && ( <> - ) : null} + )} ); }, diff --git a/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts b/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts index a5ee387bca0..469b4c69adb 100644 --- a/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts +++ b/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts @@ -400,44 +400,38 @@ function rulerRuleToCombinedRule( namespace: CombinedRuleNamespace, group: CombinedRuleGroup ): CombinedRule { - return rulerRuleType.dataSource.alertingRule(rule) - ? { - name: rule.alert, - query: rule.expr, - labels: rule.labels || {}, - annotations: rule.annotations || {}, - rulerRule: rule, - namespace, - group, - instanceTotals: {}, - filteredInstanceTotals: {}, - uid: rulerRuleType.grafana.rule(rule) ? rule.grafana_alert.uid : undefined, - } - : rulerRuleType.dataSource.recordingRule(rule) - ? { - name: rule.record, - query: rule.expr, - labels: rule.labels || {}, - annotations: {}, - rulerRule: rule, - namespace, - group, - instanceTotals: {}, - filteredInstanceTotals: {}, - uid: rulerRuleType.grafana.rule(rule) ? rule.grafana_alert.uid : undefined, - } - : { - name: rule.grafana_alert.title, - query: '', - labels: rule.labels || {}, - annotations: rule.annotations || {}, - rulerRule: rule, - namespace, - group, - instanceTotals: {}, - filteredInstanceTotals: {}, - uid: rulerRuleType.grafana.rule(rule) ? rule.grafana_alert.uid : undefined, - }; + const commonProps = { + labels: rule.labels || {}, + rulerRule: rule, + namespace, + group, + instanceTotals: {}, + filteredInstanceTotals: {}, + uid: rulerRuleType.grafana.rule(rule) ? rule.grafana_alert.uid : undefined, + }; + + if (rulerRuleType.dataSource.alertingRule(rule)) { + return { + ...commonProps, + name: rule.alert, + query: rule.expr, + annotations: rule.annotations || {}, + }; + } + if (rulerRuleType.dataSource.recordingRule(rule)) { + return { + ...commonProps, + name: rule.record, + query: rule.expr, + annotations: {}, + }; + } + return { + ...commonProps, + name: rule.grafana_alert.title, + query: '', + annotations: rule.annotations || {}, + }; } // find existing rule in group that matches the given prom rule diff --git a/public/app/features/alerting/unified/rule-editor/formDefaults.ts b/public/app/features/alerting/unified/rule-editor/formDefaults.ts index 0dbe86bca2a..459d1408b99 100644 --- a/public/app/features/alerting/unified/rule-editor/formDefaults.ts +++ b/public/app/features/alerting/unified/rule-editor/formDefaults.ts @@ -36,6 +36,15 @@ export const DEFAULT_GROUP_EVALUATION_INTERVAL = formatPrometheusDuration( ); export const getDefaultFormValues = (): RuleFormValues => { const { canCreateGrafanaRules, canCreateCloudRules } = getRulesAccess(); + const type = (() => { + if (canCreateGrafanaRules) { + return RuleFormType.grafana; + } + if (canCreateCloudRules) { + return RuleFormType.cloudAlerting; + } + return undefined; + })(); return Object.freeze({ name: '', @@ -43,7 +52,7 @@ export const getDefaultFormValues = (): RuleFormValues => { labels: [{ key: '', value: '' }], annotations: defaultAnnotations, dataSourceName: GRAFANA_RULES_SOURCE_NAME, // let's use Grafana-managed alert rule by default - type: canCreateGrafanaRules ? RuleFormType.grafana : canCreateCloudRules ? RuleFormType.cloudAlerting : undefined, // viewers can't create prom alerts + type, // viewers can't create prom alerts group: '', // grafana From a47a155a8912cb3201a3237b5430144a08a70ca4 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Wed, 23 Apr 2025 12:14:27 +0100 Subject: [PATCH 045/146] Alerting: Only show DMA button when query datasource supports it (#104158) --- .../QueryAndExpressionsStep.tsx | 8 ++- .../SmartAlertTypeDetector.tsx | 61 +---------------- .../query-and-alert-condition/utils.ts | 65 +++++++++++++++++++ .../unified/components/rules/CloudRules.tsx | 2 +- 4 files changed, 74 insertions(+), 62 deletions(-) create mode 100644 public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/utils.ts diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx index ebb273dc03f..bc07dbb70a9 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx @@ -78,6 +78,7 @@ import { } from './reducer'; import { useAdvancedMode } from './useAdvancedMode'; import { useAlertQueryRunner } from './useAlertQueryRunner'; +import { onlyOneDSInQueries } from './utils'; interface Props { editingExistingRule: boolean; @@ -474,7 +475,10 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange, mod } : undefined; - const hasDatasourcesForDataSourceManaged = Boolean(rulesSourcesWithRuler.length); + const canSelectDataSourceManaged = + onlyOneDSInQueries(queries) && + Boolean(rulesSourcesWithRuler.length) && + queries.some((query) => rulesSourcesWithRuler.some((source) => source.uid === query.datasourceUid)); return ( <> @@ -594,7 +598,7 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange, mod )} {/* We only show Switch for Grafana managed alerts */} - {hasDatasourcesForDataSourceManaged && isGrafanaAlertingType && !simplifiedQueryStep && mode === 'edit' && ( + {canSelectDataSourceManaged && isGrafanaAlertingType && !simplifiedQueryStep && mode === 'edit' && ( <> { - return queries.filter((q) => q.datasourceUid !== ExpressionDatasourceUID).length === 1; -}; -const getCanSwitch = ({ - queries, - ruleFormType, - rulesSourcesWithRuler, -}: { - rulesSourcesWithRuler: Array>; - queries: AlertQuery[]; - ruleFormType: RuleFormType | undefined; -}) => { - // get available rule types - const availableRuleTypes = getAvailableRuleTypes(); - - // check if we have only one query in queries and if it's a cloud datasource - const onlyOneDS = onlyOneDSInQueries(queries); - const dataSourceIdFromQueries = queries[0]?.datasourceUid ?? ''; - const isRecordingRuleType = ruleFormType === RuleFormType.cloudRecording; - - //let's check if we switch to cloud type - const canSwitchToCloudRule = - !isRecordingRuleType && - onlyOneDS && - rulesSourcesWithRuler.some((dsJsonData) => dsJsonData.uid === dataSourceIdFromQueries); - - const canSwitchToGrafanaRule = !isRecordingRuleType; - // check for enabled types - const grafanaTypeEnabled = availableRuleTypes.enabledRuleTypes.includes(RuleFormType.grafana); - const cloudTypeEnabled = availableRuleTypes.enabledRuleTypes.includes(RuleFormType.cloudAlerting); - - // can we switch to the other type? (cloud or grafana) - const canSwitchFromCloudToGrafana = - ruleFormType === RuleFormType.cloudAlerting && grafanaTypeEnabled && canSwitchToGrafanaRule; - const canSwitchFromGrafanaToCloud = - ruleFormType === RuleFormType.grafana && canSwitchToCloudRule && cloudTypeEnabled && canSwitchToCloudRule; - - return canSwitchFromCloudToGrafana || canSwitchFromGrafanaToCloud; -}; - -export interface SmartAlertTypeDetectorProps { +interface SmartAlertTypeDetectorProps { editingExistingRule: boolean; rulesSourcesWithRuler: Array>; queries: AlertQuery[]; diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/utils.ts b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/utils.ts new file mode 100644 index 00000000000..6ad5dac7d73 --- /dev/null +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/utils.ts @@ -0,0 +1,65 @@ +import { DataSourceInstanceSettings } from '@grafana/data'; +import { DataSourceJsonData } from '@grafana/schema/dist/esm/index'; +import { contextSrv } from 'app/core/core'; +import { ExpressionDatasourceUID } from 'app/features/expressions/types'; +import { AccessControlAction } from 'app/types'; +import { AlertQuery } from 'app/types/unified-alerting-dto'; + +import { RuleFormType } from '../../../types/rule-form'; + +export const onlyOneDSInQueries = (queries: AlertQuery[]) => { + return queries.filter((q) => q.datasourceUid !== ExpressionDatasourceUID).length === 1; +}; + +function getAvailableRuleTypes() { + const canCreateGrafanaRules = contextSrv.hasPermission(AccessControlAction.AlertingRuleCreate); + const canCreateCloudRules = contextSrv.hasPermission(AccessControlAction.AlertingRuleExternalWrite); + const defaultRuleType = canCreateGrafanaRules ? RuleFormType.grafana : RuleFormType.cloudAlerting; + + const enabledRuleTypes: RuleFormType[] = []; + if (canCreateGrafanaRules) { + enabledRuleTypes.push(RuleFormType.grafana); + } + if (canCreateCloudRules) { + enabledRuleTypes.push(RuleFormType.cloudAlerting, RuleFormType.cloudRecording); + } + + return { enabledRuleTypes, defaultRuleType }; +} + +export const getCanSwitch = ({ + queries, + ruleFormType, + rulesSourcesWithRuler, +}: { + rulesSourcesWithRuler: Array>; + queries: AlertQuery[]; + ruleFormType: RuleFormType | undefined; +}) => { + // get available rule types + const availableRuleTypes = getAvailableRuleTypes(); + + // check if we have only one query in queries and if it's a cloud datasource + const onlyOneDS = onlyOneDSInQueries(queries); + const dataSourceIdFromQueries = queries[0]?.datasourceUid ?? ''; + const isRecordingRuleType = ruleFormType === RuleFormType.cloudRecording; + + //let's check if we switch to cloud type + const canSwitchToCloudRule = + !isRecordingRuleType && + onlyOneDS && + rulesSourcesWithRuler.some((dsJsonData) => dsJsonData.uid === dataSourceIdFromQueries); + + const canSwitchToGrafanaRule = !isRecordingRuleType; + // check for enabled types + const grafanaTypeEnabled = availableRuleTypes.enabledRuleTypes.includes(RuleFormType.grafana); + const cloudTypeEnabled = availableRuleTypes.enabledRuleTypes.includes(RuleFormType.cloudAlerting); + + // can we switch to the other type? (cloud or grafana) + const canSwitchFromCloudToGrafana = + ruleFormType === RuleFormType.cloudAlerting && grafanaTypeEnabled && canSwitchToGrafanaRule; + const canSwitchFromGrafanaToCloud = + ruleFormType === RuleFormType.grafana && canSwitchToCloudRule && cloudTypeEnabled && canSwitchToCloudRule; + + return canSwitchFromCloudToGrafana || canSwitchFromGrafanaToCloud; +}; diff --git a/public/app/features/alerting/unified/components/rules/CloudRules.tsx b/public/app/features/alerting/unified/components/rules/CloudRules.tsx index 4f39b1dce3d..bb8e5849ea4 100644 --- a/public/app/features/alerting/unified/components/rules/CloudRules.tsx +++ b/public/app/features/alerting/unified/components/rules/CloudRules.tsx @@ -75,7 +75,7 @@ export const CloudRules = ({ namespaces, expandAll }: Props) => {
)} - {canMigrateToGMA && } + {canMigrateToGMA && hasSomeResults && }
From cd8556026eb17341b5c06ca40dcf9266ae69aa91 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Wed, 23 Apr 2025 12:38:10 +0100 Subject: [PATCH 046/146] Chore: Fix re-exported `skipToken` from dashboards API (#104387) --- .betterer.results | 3 --- .../core/components/NestedFolderPicker/NestedFolderPicker.tsx | 3 ++- public/app/features/browse-dashboards/BrowseDashboardsPage.tsx | 3 ++- .../app/features/browse-dashboards/api/browseDashboardsAPI.ts | 2 -- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.betterer.results b/.betterer.results index f84c6215f19..2518e0548be 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1082,9 +1082,6 @@ exports[`better eslint`] = { "public/app/features/auth-config/utils/data.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], - "public/app/features/browse-dashboards/api/browseDashboardsAPI.ts:5381": [ - [0, 0, 0, "Do not re-export imported variable (\`@reduxjs/toolkit/query/react\`)", "0"] - ], "public/app/features/browse-dashboards/state/index.ts:5381": [ [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"], [0, 0, 0, "Do not use export all (\`export * from ...\`)", "1"], diff --git a/public/app/core/components/NestedFolderPicker/NestedFolderPicker.tsx b/public/app/core/components/NestedFolderPicker/NestedFolderPicker.tsx index 83eedefef29..db450642286 100644 --- a/public/app/core/components/NestedFolderPicker/NestedFolderPicker.tsx +++ b/public/app/core/components/NestedFolderPicker/NestedFolderPicker.tsx @@ -1,5 +1,6 @@ import { css } from '@emotion/css'; import { autoUpdate, flip, useClick, useDismiss, useFloating, useInteractions } from '@floating-ui/react'; +import { skipToken } from '@reduxjs/toolkit/query'; import debounce from 'debounce-promise'; import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'; import * as React from 'react'; @@ -8,7 +9,7 @@ import { GrafanaTheme2 } from '@grafana/data'; import { config } from '@grafana/runtime'; import { Alert, Icon, Input, LoadingBar, Stack, Text, useStyles2 } from '@grafana/ui'; import { t } from 'app/core/internationalization'; -import { skipToken, useGetFolderQuery } from 'app/features/browse-dashboards/api/browseDashboardsAPI'; +import { useGetFolderQuery } from 'app/features/browse-dashboards/api/browseDashboardsAPI'; import { DashboardViewItemWithUIItems, DashboardsTreeItem } from 'app/features/browse-dashboards/types'; import { getGrafanaSearcher } from 'app/features/search/service/searcher'; import { QueryResponse } from 'app/features/search/service/types'; diff --git a/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx b/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx index 287c972a70f..a41cc80d206 100644 --- a/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx +++ b/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx @@ -1,4 +1,5 @@ import { css } from '@emotion/css'; +import { skipToken } from '@reduxjs/toolkit/query'; import { memo, useEffect, useMemo } from 'react'; import { useLocation, useParams } from 'react-router-dom-v5-compat'; import AutoSizer from 'react-virtualized-auto-sizer'; @@ -18,7 +19,7 @@ import { buildNavModel, getDashboardsTabID } from '../folders/state/navModel'; import { useSearchStateManager } from '../search/state/SearchStateManager'; import { getSearchPlaceholder } from '../search/tempI18nPhrases'; -import { skipToken, useGetFolderQuery, useSaveFolderMutation } from './api/browseDashboardsAPI'; +import { useGetFolderQuery, useSaveFolderMutation } from './api/browseDashboardsAPI'; import { BrowseActions } from './components/BrowseActions/BrowseActions'; import { BrowseFilters } from './components/BrowseFilters'; import { BrowseView } from './components/BrowseView'; diff --git a/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts b/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts index accd12757bf..89749f285d5 100644 --- a/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts +++ b/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts @@ -443,5 +443,3 @@ export const { useRestoreDashboardMutation, useHardDeleteDashboardMutation, } = browseDashboardsAPI; - -export { skipToken } from '@reduxjs/toolkit/query/react'; From 9b325438f75647c83bd1e58703f68e99a83f2539 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 23 Apr 2025 13:51:37 +0200 Subject: [PATCH 047/146] Chore: Upgrade authlib (use UIDs for authz checks) (#104307) --- go.mod | 2 +- go.sum | 12 ++++++++++-- go.work.sum | 8 +------- pkg/apimachinery/go.mod | 2 +- pkg/apimachinery/go.sum | 4 ++-- pkg/apiserver/go.mod | 2 +- pkg/apiserver/go.sum | 4 ++-- pkg/storage/unified/apistore/go.mod | 2 +- pkg/storage/unified/apistore/go.sum | 3 +-- pkg/storage/unified/resource/go.mod | 2 +- pkg/storage/unified/resource/go.sum | 4 ++-- 11 files changed, 23 insertions(+), 22 deletions(-) diff --git a/go.mod b/go.mod index bae25dda429..054f66e485a 100644 --- a/go.mod +++ b/go.mod @@ -78,7 +78,7 @@ require ( github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group github.com/gorilla/websocket v1.5.3 // @grafana/grafana-app-platform-squad github.com/grafana/alerting v0.0.0-20250418140648-7268b2b6d9f1 // @grafana/alerting-backend - github.com/grafana/authlib v0.0.0-20250325095148-d6da9c164a7d // @grafana/identity-access-team + github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a // @grafana/identity-access-team github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d // @grafana/identity-access-team github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics github.com/grafana/dataplane/sdata v0.0.9 // @grafana/observability-metrics diff --git a/go.sum b/go.sum index c3879ce57a1..012e22ebd0e 100644 --- a/go.sum +++ b/go.sum @@ -1567,8 +1567,8 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/alerting v0.0.0-20250418140648-7268b2b6d9f1 h1:xdTzIQqFJxzRLgODsFMAT/8LIWtqCXLqltaWo6AmFVA= github.com/grafana/alerting v0.0.0-20250418140648-7268b2b6d9f1/go.mod h1:XTbf+jPVVMC1C2NuSGa3hIVbO+KSQD0qHjiUr1GoWVI= -github.com/grafana/authlib v0.0.0-20250325095148-d6da9c164a7d h1:TDVZemfYeJHPyXeYCnqL7BQqsa+mpaZYth/Qm3TKaT8= -github.com/grafana/authlib v0.0.0-20250325095148-d6da9c164a7d/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us= +github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a h1:irEH0Abl6mKbkPx/xtmB5Wai4ipzEB6hGPNsLya/p9Y= +github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us= github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d h1:34E6btDAhdDOiSEyrMaYaHwnJpM8w9QKzVQZIBzLNmM= github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/grafana/dataplane/examples v0.0.1 h1:K9M5glueWyLoL4//H+EtTQq16lXuHLmOhb6DjSCahzA= @@ -1602,23 +1602,31 @@ github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79/ github.com/grafana/grafana-plugin-sdk-go v0.277.0 h1:VDU2F4Y5NeRS//ejctdZtsAshrGaEdbtW33FsK0EQss= github.com/grafana/grafana-plugin-sdk-go v0.277.0/go.mod h1:mAUWg68w5+1f5TLDqagIr8sWr1RT9h7ufJl5NMcWJAU= github.com/grafana/grafana/apps/advisor v0.0.0-20250422074709-7c8433fbb2c2 h1:IoXfNDcVQLh4/9pjKqm2MUz1oo5mJnUCtb3tst/GIHA= +github.com/grafana/grafana/apps/advisor v0.0.0-20250422074709-7c8433fbb2c2/go.mod h1:xOL9buMMbQg+3m0jPfrza4/5iwe4EBrnur/aJGAA1pM= github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250422074709-7c8433fbb2c2 h1:5MQ9mLe/3t2oExmvhnUgqhj9N1+3swjFjVhd/rbKaEs= +github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250422074709-7c8433fbb2c2/go.mod h1:hfz29ggGyj8XNPNzvkz9jaMms5a6/LZkZQWfvaQGPK0= github.com/grafana/grafana/apps/dashboard v0.0.0-20250422074709-7c8433fbb2c2 h1:AaDJAh7JnBXmMAagob+ewlIUiWPSxQuCESM34EBK5wM= github.com/grafana/grafana/apps/dashboard v0.0.0-20250422074709-7c8433fbb2c2/go.mod h1:LBRNpF2LR2ktSk44HcT9l0SOXKZOn4svTYKxBHCBhtc= github.com/grafana/grafana/apps/folder v0.0.0-20250422074709-7c8433fbb2c2 h1:XGyEA0CHFb7noYqN/E6hW5Hvtvw/agLEkXV0SdgIwNg= github.com/grafana/grafana/apps/folder v0.0.0-20250422074709-7c8433fbb2c2/go.mod h1:lpi6D5h/zQMUI5VqiV6lDomXyTQA1IZSYyuqyn7xFK4= github.com/grafana/grafana/apps/investigations v0.0.0-20250422074709-7c8433fbb2c2 h1:GIcowS7OuHaTiZEwLscPPPCGc9Qv3+UtscJZIXHcVLo= +github.com/grafana/grafana/apps/investigations v0.0.0-20250422074709-7c8433fbb2c2/go.mod h1:pI3xLwHRhaMTYO5pgA34/ros5suc5J1H+HAdRfwlbx4= github.com/grafana/grafana/apps/playlist v0.0.0-20250422074709-7c8433fbb2c2 h1:J0cA0yYx/6MB5qa9VIMlsT5rlZAgGtJe2URt2I4GHT0= +github.com/grafana/grafana/apps/playlist v0.0.0-20250422074709-7c8433fbb2c2/go.mod h1:9U44mptAJW8bkvgPgCxsnki58/nz3wKPgDayeyeFWJs= github.com/grafana/grafana/pkg/aggregator v0.0.0-20250422074709-7c8433fbb2c2 h1:a7OgvUdcGHBTwVjejQXqH1q1C5kBz3ZRYiiHHZdRjeM= +github.com/grafana/grafana/pkg/aggregator v0.0.0-20250422074709-7c8433fbb2c2/go.mod h1:GHOXRpm34Y827pUToxwKgM2ZEf2tyuFHdtHYOgAOxrI= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250422074709-7c8433fbb2c2 h1:kvG92f3XbJlQPUcZfXlTNLziI4e8LYeA9Jv2ixmM5Ic= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250422074709-7c8433fbb2c2/go.mod h1:ll14OJrUGYgXApz3YX6zmxYjRMZHL+pgQjoKBuRzaRs= github.com/grafana/grafana/pkg/apis/secret v0.0.0-20250422074709-7c8433fbb2c2 h1:fwsq+3uDUcmmV91ly4fESt3U4gDlGXbB6389S5ueljA= +github.com/grafana/grafana/pkg/apis/secret v0.0.0-20250422074709-7c8433fbb2c2/go.mod h1:bszKnjm4DxPx96DEYduncSVDxfFz14NwW0+bntRdjY0= github.com/grafana/grafana/pkg/apiserver v0.0.0-20250422074709-7c8433fbb2c2 h1:ZreXete9lRBJmBe49OHeYh8yo+MyXDs5q/96mlRnr0s= github.com/grafana/grafana/pkg/apiserver v0.0.0-20250422074709-7c8433fbb2c2/go.mod h1:Xaz4wiMdzfuMqzxZ5ZhNwzBCGGJVn/IQfzDSY5aosQY= github.com/grafana/grafana/pkg/promlib v0.0.8 h1:VUWsqttdf0wMI4j9OX9oNrykguQpZcruudDAFpJJVw0= github.com/grafana/grafana/pkg/promlib v0.0.8/go.mod h1:U1ezG/MGaEPoThqsr3lymMPN5yIPdVTJnDZ+wcXT+ao= github.com/grafana/grafana/pkg/semconv v0.0.0-20250422074709-7c8433fbb2c2 h1:uKOBkqzjMwimPJvTOjlo0bFrrR17w8U5l3HtDETPacQ= +github.com/grafana/grafana/pkg/semconv v0.0.0-20250422074709-7c8433fbb2c2/go.mod h1:w5oIOh8JhAEY/GwiIrLGBBRv2w0D7Ngv+dznv4k8Tek= github.com/grafana/grafana/pkg/storage/unified/apistore v0.0.0-20250422074709-7c8433fbb2c2 h1:LtuJWMxi64Zm43AVirn1uNu6SYMmnwkRAfigvm3tpB8= +github.com/grafana/grafana/pkg/storage/unified/apistore v0.0.0-20250422074709-7c8433fbb2c2/go.mod h1:LivE9S7HU1uU0cZ99wG77ZgPmOPZYuFWfZ68Lh59gPU= github.com/grafana/grafana/pkg/storage/unified/resource v0.0.0-20250422074709-7c8433fbb2c2 h1:1f8d/Jy/9kv4bqtI5dQjxhpzFBWFrmtPXAPjOd8e6WA= github.com/grafana/grafana/pkg/storage/unified/resource v0.0.0-20250422074709-7c8433fbb2c2/go.mod h1:c1wMG6p6/zlMsi1KoOGYNMdFW2f8xM690CSZcl2i4eI= github.com/grafana/grafana/pkg/util/xorm v0.0.1 h1:72QZjxWIWpSeOF8ob4aMV058kfgZyeetkAB8dmeti2o= diff --git a/go.work.sum b/go.work.sum index 40878add7a3..60f369552b9 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1121,24 +1121,17 @@ github.com/grafana/grafana-plugin-sdk-go v0.267.0/go.mod h1:OuwS4c/JYgn0rr/w5zhJ github.com/grafana/grafana-plugin-sdk-go v0.269.1/go.mod h1:yv2KbO4mlr9WuDK2f+2gHAMTwwLmLuqaEnrPXTRU+OI= github.com/grafana/grafana/apps/advisor v0.0.0-20250123151950-b066a6313173/go.mod h1:goSDiy3jtC2cp8wjpPZdUHRENcoSUHae1/Px/MDfddA= github.com/grafana/grafana/apps/advisor v0.0.0-20250220154326-6e5de80ef295/go.mod h1:9I1dKV3Dqr0NPR9Af0WJGxOytp5/6W3JLiNChOz8r+c= -github.com/grafana/grafana/apps/advisor v0.0.0-20250422074709-7c8433fbb2c2/go.mod h1:xOL9buMMbQg+3m0jPfrza4/5iwe4EBrnur/aJGAA1pM= github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250121113133-e747350fee2d/go.mod h1:AvleS6icyPmcBjihtx5jYEvdzLmHGBp66NuE0AMR57A= github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250416173722-ec17e0e4ce03/go.mod h1:oemrhKvFxxc5m32xKHPxInEHAObH0/hPPyHUiBUZ1Cc= -github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250422074709-7c8433fbb2c2/go.mod h1:hfz29ggGyj8XNPNzvkz9jaMms5a6/LZkZQWfvaQGPK0= github.com/grafana/grafana/apps/investigation v0.0.0-20250121113133-e747350fee2d/go.mod h1:HQprw3MmiYj5OUV9CZnkwA1FKDZBmYACuAB3oDvUOmI= -github.com/grafana/grafana/apps/investigations v0.0.0-20250422074709-7c8433fbb2c2/go.mod h1:pI3xLwHRhaMTYO5pgA34/ros5suc5J1H+HAdRfwlbx4= github.com/grafana/grafana/apps/playlist v0.0.0-20250121113133-e747350fee2d/go.mod h1:DjJe5osrW/BKrzN9hAAOSElNWutj1bcriExa7iDP7kA= -github.com/grafana/grafana/apps/playlist v0.0.0-20250422074709-7c8433fbb2c2/go.mod h1:9U44mptAJW8bkvgPgCxsnki58/nz3wKPgDayeyeFWJs= github.com/grafana/grafana/pkg/aggregator v0.0.0-20250121113133-e747350fee2d/go.mod h1:1sq0guad+G4SUTlBgx7SXfhnzy7D86K/LcVOtiQCiMA= -github.com/grafana/grafana/pkg/aggregator v0.0.0-20250422074709-7c8433fbb2c2/go.mod h1:GHOXRpm34Y827pUToxwKgM2ZEf2tyuFHdtHYOgAOxrI= -github.com/grafana/grafana/pkg/apis/secret v0.0.0-20250422074709-7c8433fbb2c2/go.mod h1:bszKnjm4DxPx96DEYduncSVDxfFz14NwW0+bntRdjY0= github.com/grafana/grafana/pkg/build v0.0.0-20250220114259-be81314e2118/go.mod h1:STVpVboMYeBAfyn6Zw6XHhTHqUxzMy7pzRiVgk1l0W0= github.com/grafana/grafana/pkg/build v0.0.0-20250227105625-8f465f124924/go.mod h1:Vw0LdoMma64VgIMVpRY3i0D156jddgUGjTQBOcyeF3k= github.com/grafana/grafana/pkg/build v0.0.0-20250227163402-d78c646f93bb/go.mod h1:Vw0LdoMma64VgIMVpRY3i0D156jddgUGjTQBOcyeF3k= github.com/grafana/grafana/pkg/build v0.0.0-20250403075254-4918d8720c61/go.mod h1:LGVnSwdrS0ZnJ2WXEl5acgDoYPm74EUSFavca1NKHI8= github.com/grafana/grafana/pkg/semconv v0.0.0-20250121113133-e747350fee2d/go.mod h1:tfLnBpPYgwrBMRz4EXqPCZJyCjEG4Ev37FSlXnocJ2c= github.com/grafana/grafana/pkg/storage/unified/apistore v0.0.0-20250121113133-e747350fee2d/go.mod h1:CXpwZ3Mkw6xVlGKc0SqUxqXCP3Uv182q6qAQnLaLxRg= -github.com/grafana/grafana/pkg/storage/unified/apistore v0.0.0-20250422074709-7c8433fbb2c2/go.mod h1:LivE9S7HU1uU0cZ99wG77ZgPmOPZYuFWfZ68Lh59gPU= github.com/grafana/prometheus-alertmanager v0.25.1-0.20240930132144-b5e64e81e8d3 h1:6D2gGAwyQBElSrp3E+9lSr7k8gLuP3Aiy20rweLWeBw= github.com/grafana/prometheus-alertmanager v0.25.1-0.20240930132144-b5e64e81e8d3/go.mod h1:YeND+6FDA7OuFgDzYODN8kfPhXLCehcpxe4T9mdnpCY= github.com/grafana/prometheus-alertmanager v0.25.1-0.20250331083058-4563aec7a975 h1:4/BZkGObFWZf4cLbE2Vqg/1VTz67Q0AJ7LHspWLKJoQ= @@ -1890,6 +1883,7 @@ golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ss golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index 36fcfb269cb..03a1fe7c86a 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/pkg/apimachinery go 1.24.2 require ( - github.com/grafana/authlib v0.0.0-20250325095148-d6da9c164a7d // @grafana/identity-access-team + github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a // @grafana/identity-access-team github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d // @grafana/identity-access-team github.com/stretchr/testify v1.10.0 k8s.io/apimachinery v0.32.3 diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index 909be513730..4387e968caa 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -32,8 +32,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/authlib v0.0.0-20250325095148-d6da9c164a7d h1:TDVZemfYeJHPyXeYCnqL7BQqsa+mpaZYth/Qm3TKaT8= -github.com/grafana/authlib v0.0.0-20250325095148-d6da9c164a7d/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us= +github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a h1:irEH0Abl6mKbkPx/xtmB5Wai4ipzEB6hGPNsLya/p9Y= +github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us= github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d h1:34E6btDAhdDOiSEyrMaYaHwnJpM8w9QKzVQZIBzLNmM= github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index c6695f08ced..af002978e28 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -45,7 +45,7 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/grafana/authlib v0.0.0-20250325095148-d6da9c164a7d // indirect + github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index 96308cd5428..e5a25d6f775 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -81,8 +81,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/authlib v0.0.0-20250325095148-d6da9c164a7d h1:TDVZemfYeJHPyXeYCnqL7BQqsa+mpaZYth/Qm3TKaT8= -github.com/grafana/authlib v0.0.0-20250325095148-d6da9c164a7d/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us= +github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a h1:irEH0Abl6mKbkPx/xtmB5Wai4ipzEB6hGPNsLya/p9Y= +github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us= github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d h1:34E6btDAhdDOiSEyrMaYaHwnJpM8w9QKzVQZIBzLNmM= github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/grafana/grafana-app-sdk/logging v0.35.1 h1:taVpl+RoixTYl0JBJGhH+fPVmwA9wvdwdzJTZsv9buM= diff --git a/pkg/storage/unified/apistore/go.mod b/pkg/storage/unified/apistore/go.mod index 7f1f17d4926..9b10cca536e 100644 --- a/pkg/storage/unified/apistore/go.mod +++ b/pkg/storage/unified/apistore/go.mod @@ -118,7 +118,7 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grafana/authlib v0.0.0-20250325095148-d6da9c164a7d // indirect + github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a // indirect github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 // indirect github.com/grafana/grafana-app-sdk v0.35.1 // indirect github.com/grafana/grafana-app-sdk/logging v0.35.1 // indirect diff --git a/pkg/storage/unified/apistore/go.sum b/pkg/storage/unified/apistore/go.sum index 40805f37d35..61fae46133a 100644 --- a/pkg/storage/unified/apistore/go.sum +++ b/pkg/storage/unified/apistore/go.sum @@ -280,8 +280,7 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/authlib v0.0.0-20250325095148-d6da9c164a7d h1:TDVZemfYeJHPyXeYCnqL7BQqsa+mpaZYth/Qm3TKaT8= -github.com/grafana/authlib v0.0.0-20250325095148-d6da9c164a7d/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us= +github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a h1:irEH0Abl6mKbkPx/xtmB5Wai4ipzEB6hGPNsLya/p9Y= github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d h1:34E6btDAhdDOiSEyrMaYaHwnJpM8w9QKzVQZIBzLNmM= github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 h1:IR+UNYHqaU31t8/TArJk8K/GlDwOyxMpGNkWCXeZ28g= diff --git a/pkg/storage/unified/resource/go.mod b/pkg/storage/unified/resource/go.mod index 9d927896b5b..2073290530d 100644 --- a/pkg/storage/unified/resource/go.mod +++ b/pkg/storage/unified/resource/go.mod @@ -13,7 +13,7 @@ require ( github.com/fullstorydev/grpchan v1.1.1 github.com/go-jose/go-jose/v3 v3.0.4 github.com/google/uuid v1.6.0 - github.com/grafana/authlib v0.0.0-20250325095148-d6da9c164a7d + github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 github.com/grafana/grafana-app-sdk/logging v0.35.1 diff --git a/pkg/storage/unified/resource/go.sum b/pkg/storage/unified/resource/go.sum index 8ce7fc9f966..acaf9f37dbc 100644 --- a/pkg/storage/unified/resource/go.sum +++ b/pkg/storage/unified/resource/go.sum @@ -269,8 +269,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1 github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grafana/authlib v0.0.0-20250325095148-d6da9c164a7d h1:TDVZemfYeJHPyXeYCnqL7BQqsa+mpaZYth/Qm3TKaT8= -github.com/grafana/authlib v0.0.0-20250325095148-d6da9c164a7d/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us= +github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a h1:irEH0Abl6mKbkPx/xtmB5Wai4ipzEB6hGPNsLya/p9Y= +github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us= github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d h1:34E6btDAhdDOiSEyrMaYaHwnJpM8w9QKzVQZIBzLNmM= github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 h1:IR+UNYHqaU31t8/TArJk8K/GlDwOyxMpGNkWCXeZ28g= From 0743689d424cf63364b5a973b781f58f03b0f307 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Wed, 23 Apr 2025 13:58:57 +0200 Subject: [PATCH 048/146] Alerting: Add recovering state to the grafana_alerting_alerts metric (#104380) --- pkg/services/ngalert/state/cache.go | 1 + pkg/services/ngalert/state/cache_test.go | 69 ++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/pkg/services/ngalert/state/cache.go b/pkg/services/ngalert/state/cache.go index a7302fc5518..2b311e9d2b4 100644 --- a/pkg/services/ngalert/state/cache.go +++ b/pkg/services/ngalert/state/cache.go @@ -52,6 +52,7 @@ func (c *cache) RegisterMetrics(r prometheus.Registerer) { r.MustRegister(newAlertCountByState(eval.Pending)) r.MustRegister(newAlertCountByState(eval.Error)) r.MustRegister(newAlertCountByState(eval.NoData)) + r.MustRegister(newAlertCountByState(eval.Recovering)) } func (c *cache) countAlertsBy(state eval.State) float64 { diff --git a/pkg/services/ngalert/state/cache_test.go b/pkg/services/ngalert/state/cache_test.go index 6defc25086d..f83c1aa9698 100644 --- a/pkg/services/ngalert/state/cache_test.go +++ b/pkg/services/ngalert/state/cache_test.go @@ -1,6 +1,7 @@ package state import ( + "bytes" "context" "errors" "math/rand" @@ -8,6 +9,8 @@ import ( "time" "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/infra/log" @@ -147,6 +150,72 @@ func Test_mergeLabels(t *testing.T) { }) } +func TestCacheMetrics(t *testing.T) { + orgID := int64(1) + + t.Run("should return metrics for all states", func(t *testing.T) { + states := []*State{ + { + OrgID: orgID, + AlertRuleUID: "rule1", + CacheID: data.Fingerprint(rand.Int63()), + State: eval.Normal, + }, + { + OrgID: orgID, + AlertRuleUID: "rule1", + CacheID: data.Fingerprint(rand.Int63()), + State: eval.Alerting, + }, + { + OrgID: orgID, + AlertRuleUID: "rule1", + CacheID: data.Fingerprint(rand.Int63()), + State: eval.Pending, + }, + { + OrgID: orgID, + AlertRuleUID: "rule1", + CacheID: data.Fingerprint(rand.Int63()), + State: eval.Error, + }, + { + OrgID: orgID, + AlertRuleUID: "rule1", + CacheID: data.Fingerprint(rand.Int63()), + State: eval.NoData, + }, + { + OrgID: orgID, + AlertRuleUID: "rule1", + CacheID: data.Fingerprint(rand.Int63()), + State: eval.Recovering, + }, + } + expectedMetrics := ` + # HELP grafana_alerting_alerts How many alerts by state are in the scheduler. + # TYPE grafana_alerting_alerts gauge + grafana_alerting_alerts{state="alerting"} 1 + grafana_alerting_alerts{state="error"} 1 + grafana_alerting_alerts{state="nodata"} 1 + grafana_alerting_alerts{state="normal"} 1 + grafana_alerting_alerts{state="pending"} 1 + grafana_alerting_alerts{state="recovering"} 1 + ` + + reg := prometheus.NewPedanticRegistry() + cache := newCache() + for _, state := range states { + cache.set(state) + } + + cache.RegisterMetrics(reg) + + err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetrics), "grafana_alerting_alerts") + require.NoError(t, err) + }) +} + func randomSate(ruleKey models.AlertRuleKey) State { return State{ OrgID: ruleKey.OrgID, From ed27d76d7fb921dbb763b4b79bfd356fb0c5b614 Mon Sep 17 00:00:00 2001 From: Juan Cabanas Date: Wed, 23 Apr 2025 09:13:58 -0300 Subject: [PATCH 049/146] Grafana UI: Add `ref` to `TagsInput` (#104344) --- .../src/components/TagsInput/TagsInput.tsx | 152 +++++++++--------- 1 file changed, 80 insertions(+), 72 deletions(-) diff --git a/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx b/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx index d0d72febaf0..bb7f23cd332 100644 --- a/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx +++ b/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx @@ -1,5 +1,5 @@ import { css, cx } from '@emotion/css'; -import { useCallback, useState } from 'react'; +import { useCallback, useState, forwardRef } from 'react'; import * as React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; @@ -29,83 +29,91 @@ export interface Props { autoColors?: boolean; } -export const TagsInput = ({ - placeholder = 'New tag (enter key to add)', - tags = [], - onChange, - width, - className, - disabled, - addOnBlur, - invalid, - id, - autoColors = true, -}: Props) => { - const [newTagName, setNewTagName] = useState(''); - const styles = useStyles2(getStyles); - const theme = useTheme2(); +export const TagsInput = forwardRef( + ( + { + placeholder = 'New tag (enter key to add)', + tags = [], + onChange, + width, + className, + disabled, + addOnBlur, + invalid, + id, + autoColors = true, + }, + ref + ) => { + const [newTagName, setNewTagName] = useState(''); + const styles = useStyles2(getStyles); + const theme = useTheme2(); - const onNameChange = useCallback((event: React.ChangeEvent) => { - setNewTagName(event.target.value); - }, []); + const onNameChange = useCallback((event: React.ChangeEvent) => { + setNewTagName(event.target.value); + }, []); - const onRemove = (tagToRemove: string) => { - onChange(tags.filter((x) => x !== tagToRemove)); - }; + const onRemove = (tagToRemove: string) => { + onChange(tags.filter((x) => x !== tagToRemove)); + }; - const onAdd = (event?: React.MouseEvent | React.KeyboardEvent) => { - event?.preventDefault(); - if (!tags.includes(newTagName)) { - onChange(tags.concat(newTagName)); - } - setNewTagName(''); - }; + const onAdd = (event?: React.MouseEvent | React.KeyboardEvent) => { + event?.preventDefault(); + if (!tags.includes(newTagName)) { + onChange(tags.concat(newTagName)); + } + setNewTagName(''); + }; - const onBlur = () => { - if (addOnBlur && newTagName) { - onAdd(); - } - }; + const onBlur = () => { + if (addOnBlur && newTagName) { + onAdd(); + } + }; - const onKeyboardAdd = (event: React.KeyboardEvent) => { - if (event.key === 'Enter' && newTagName !== '') { - onAdd(event); - } - }; + const onKeyboardAdd = (event: React.KeyboardEvent) => { + if (event.key === 'Enter' && newTagName !== '') { + onAdd(event); + } + }; - return ( -
- - Add - - } - /> - {tags?.length > 0 && ( -
    - {tags.map((tag) => ( - - ))} -
- )} -
- ); -}; + return ( +
+ + Add + + } + /> + {tags?.length > 0 && ( +
    + {tags.map((tag) => ( + + ))} +
+ )} +
+ ); + } +); + +TagsInput.displayName = 'TagsInput'; const getStyles = (theme: GrafanaTheme2) => ({ wrapper: css({ From 67ac54fb0e355393e91f7a42a817edee7f7bff28 Mon Sep 17 00:00:00 2001 From: Michael Mandrus <41969079+mmandrus@users.noreply.github.com> Date: Wed, 23 Apr 2025 08:26:45 -0400 Subject: [PATCH 050/146] Chore: Rename file containing SQLStore migrations per repo conventions (#104354) rename per conventions --- .../migrations/{folder_uid_migrator.go => folder_uid_mig.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pkg/services/dashboards/database/migrations/{folder_uid_migrator.go => folder_uid_mig.go} (100%) diff --git a/pkg/services/dashboards/database/migrations/folder_uid_migrator.go b/pkg/services/dashboards/database/migrations/folder_uid_mig.go similarity index 100% rename from pkg/services/dashboards/database/migrations/folder_uid_migrator.go rename to pkg/services/dashboards/database/migrations/folder_uid_mig.go From c8981d91c7b656f68e61460cb58c59127446a324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Jim=C3=A9nez=20S=C3=A1nchez?= Date: Wed, 23 Apr 2025 14:29:21 +0200 Subject: [PATCH 051/146] Provisioning: remaining unit test coverage github repository (#104382) * Add test for GitHubRepository_LatestRef * Add test for GitHubRepository_LatestRef * Add test for GitHubRepository_CommentPullRequest * Add remaining tests for GitHubRepository * Add remaining tests for GitHubRepository * Fix linting --- pkg/registry/apis/provisioning/register.go | 2 +- .../provisioning/repository/clone_fn_mock.go | 95 ++ .../apis/provisioning/repository/github.go | 22 +- .../provisioning/repository/github_test.go | 1517 +++++++++++++++++ .../provisioning/repository/repository.go | 1 + 5 files changed, 1628 insertions(+), 9 deletions(-) create mode 100644 pkg/registry/apis/provisioning/repository/clone_fn_mock.go diff --git a/pkg/registry/apis/provisioning/register.go b/pkg/registry/apis/provisioning/register.go index a367f5ea2be..41c3a3ac0d3 100644 --- a/pkg/registry/apis/provisioning/register.go +++ b/pkg/registry/apis/provisioning/register.go @@ -1152,7 +1152,7 @@ func (b *APIBuilder) AsRepository(ctx context.Context, r *provisioning.Repositor return gogit.Clone(ctx, b.clonedir, r, opts, b.secrets) } - return repository.NewGitHub(ctx, r, b.ghFactory, b.secrets, webhookURL, cloneFn), nil + return repository.NewGitHub(ctx, r, b.ghFactory, b.secrets, webhookURL, cloneFn) default: return nil, fmt.Errorf("unknown repository type (%s)", r.Spec.Type) } diff --git a/pkg/registry/apis/provisioning/repository/clone_fn_mock.go b/pkg/registry/apis/provisioning/repository/clone_fn_mock.go new file mode 100644 index 00000000000..f248ee243a6 --- /dev/null +++ b/pkg/registry/apis/provisioning/repository/clone_fn_mock.go @@ -0,0 +1,95 @@ +// Code generated by mockery v2.52.4. DO NOT EDIT. + +package repository + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// MockCloneFn is an autogenerated mock type for the CloneFn type +type MockCloneFn struct { + mock.Mock +} + +type MockCloneFn_Expecter struct { + mock *mock.Mock +} + +func (_m *MockCloneFn) EXPECT() *MockCloneFn_Expecter { + return &MockCloneFn_Expecter{mock: &_m.Mock} +} + +// Execute provides a mock function with given fields: ctx, opts +func (_m *MockCloneFn) Execute(ctx context.Context, opts CloneOptions) (ClonedRepository, error) { + ret := _m.Called(ctx, opts) + + if len(ret) == 0 { + panic("no return value specified for Execute") + } + + var r0 ClonedRepository + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, CloneOptions) (ClonedRepository, error)); ok { + return rf(ctx, opts) + } + if rf, ok := ret.Get(0).(func(context.Context, CloneOptions) ClonedRepository); ok { + r0 = rf(ctx, opts) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(ClonedRepository) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, CloneOptions) error); ok { + r1 = rf(ctx, opts) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockCloneFn_Execute_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Execute' +type MockCloneFn_Execute_Call struct { + *mock.Call +} + +// Execute is a helper method to define mock.On call +// - ctx context.Context +// - opts CloneOptions +func (_e *MockCloneFn_Expecter) Execute(ctx interface{}, opts interface{}) *MockCloneFn_Execute_Call { + return &MockCloneFn_Execute_Call{Call: _e.mock.On("Execute", ctx, opts)} +} + +func (_c *MockCloneFn_Execute_Call) Run(run func(ctx context.Context, opts CloneOptions)) *MockCloneFn_Execute_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(CloneOptions)) + }) + return _c +} + +func (_c *MockCloneFn_Execute_Call) Return(_a0 ClonedRepository, _a1 error) *MockCloneFn_Execute_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockCloneFn_Execute_Call) RunAndReturn(run func(context.Context, CloneOptions) (ClonedRepository, error)) *MockCloneFn_Execute_Call { + _c.Call.Return(run) + return _c +} + +// NewMockCloneFn creates a new instance of MockCloneFn. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockCloneFn(t interface { + mock.TestingT + Cleanup(func()) +}) *MockCloneFn { + mock := &MockCloneFn{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/registry/apis/provisioning/repository/github.go b/pkg/registry/apis/provisioning/repository/github.go index 577136fc94a..885fec05764 100644 --- a/pkg/registry/apis/provisioning/repository/github.go +++ b/pkg/registry/apis/provisioning/repository/github.go @@ -56,15 +56,21 @@ func NewGitHub( secrets secrets.Service, webhookURL string, cloneFn CloneFn, -) *githubRepository { - owner, repo, _ := parseOwnerRepo(config.Spec.GitHub.URL) +) (*githubRepository, error) { + owner, repo, err := parseOwnerRepo(config.Spec.GitHub.URL) + if err != nil { + return nil, fmt.Errorf("parse owner and repo: %w", err) + } + token := config.Spec.GitHub.Token if token == "" { decrypted, err := secrets.Decrypt(ctx, config.Spec.GitHub.EncryptedToken) - if err == nil { - token = string(decrypted) + if err != nil { + return nil, fmt.Errorf("decrypt token: %w", err) } + token = string(decrypted) } + return &githubRepository{ config: config, gh: factory.New(ctx, token), // TODO, baseURL from config @@ -73,7 +79,7 @@ func NewGitHub( owner: owner, repo: repo, cloneFn: cloneFn, - } + }, nil } func (r *githubRepository) Config() *provisioning.Repository { @@ -705,8 +711,8 @@ func (r *githubRepository) CompareFiles(ctx context.Context, base, ref string) ( }) case previousErr == nil && currentErr != nil: changes = append(changes, VersionedFileChange{ - Path: currentPath, - Ref: ref, + Path: previousPath, + Ref: base, Action: FileActionDeleted, }) case previousErr != nil && currentErr == nil: @@ -826,7 +832,7 @@ func (r *githubRepository) updateWebhook(ctx context.Context) (pgh.WebhookConfig var mustUpdate bool - if hook.URL != r.config.Status.Webhook.URL { + if hook.URL != r.webhookURL { mustUpdate = true hook.URL = r.webhookURL } diff --git a/pkg/registry/apis/provisioning/repository/github_test.go b/pkg/registry/apis/provisioning/repository/github_test.go index 69aa4a8b6c1..387960cbc4f 100644 --- a/pkg/registry/apis/provisioning/repository/github_test.go +++ b/pkg/registry/apis/provisioning/repository/github_test.go @@ -7,9 +7,11 @@ import ( "encoding/hex" "errors" "fmt" + "io" "net/http" "os" "path" + "slices" "strings" "testing" "time" @@ -26,6 +28,142 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets" ) +func TestNewGitHub(t *testing.T) { + tests := []struct { + name string + config *provisioning.Repository + setupMock func(m *secrets.MockService) + expectedError string + expectedRepo *githubRepository + }{ + { + name: "successful creation with token", + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + URL: "https://github.com/grafana/grafana", + Token: "token123", + Branch: "main", + }, + }, + }, + setupMock: func(m *secrets.MockService) { + // No mock calls expected since we're using the token directly + }, + expectedError: "", + expectedRepo: &githubRepository{ + owner: "grafana", + repo: "grafana", + }, + }, + { + name: "successful creation with encrypted token", + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + URL: "https://github.com/grafana/grafana", + EncryptedToken: []byte("encrypted-token"), + Branch: "main", + }, + }, + }, + setupMock: func(m *secrets.MockService) { + m.On("Decrypt", mock.Anything, []byte("encrypted-token")). + Return([]byte("decrypted-token"), nil) + }, + expectedError: "", + expectedRepo: &githubRepository{ + owner: "grafana", + repo: "grafana", + }, + }, + { + name: "error decrypting token", + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + URL: "https://github.com/grafana/grafana", + EncryptedToken: []byte("encrypted-token"), + Branch: "main", + }, + }, + }, + setupMock: func(m *secrets.MockService) { + m.On("Decrypt", mock.Anything, []byte("encrypted-token")). + Return(nil, fmt.Errorf("decryption error")) + }, + expectedError: "decrypt token: decryption error", + }, + { + name: "invalid URL format", + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + URL: "invalid-url", + Token: "token123", + Branch: "main", + }, + }, + }, + setupMock: func(m *secrets.MockService) { + // No mock calls expected + }, + expectedError: "parse owner and repo", + expectedRepo: &githubRepository{ + owner: "", + repo: "", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup mocks + mockSecrets := secrets.NewMockService(t) + if tt.setupMock != nil { + tt.setupMock(mockSecrets) + } + + factory := pgh.ProvideFactory() + factory.Client = http.DefaultClient + + // Create a mock clone function + cloneFn := func(ctx context.Context, opts CloneOptions) (ClonedRepository, error) { + return nil, nil + } + + // Call the function under test + repo, err := NewGitHub( + context.Background(), + tt.config, + factory, + mockSecrets, + "https://example.com/webhook", + cloneFn, + ) + + // Check results + if tt.expectedError != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.expectedError) + assert.Nil(t, repo) + } else { + require.NoError(t, err) + require.NotNil(t, repo) + assert.Equal(t, tt.expectedRepo.owner, repo.owner) + assert.Equal(t, tt.expectedRepo.repo, repo.repo) + assert.Equal(t, tt.config, repo.config) + assert.Equal(t, mockSecrets, repo.secrets) + assert.Equal(t, "https://example.com/webhook", repo.webhookURL) + assert.NotNil(t, repo.cloneFn) + } + + // Verify all mock expectations were met + mockSecrets.AssertExpectations(t) + }) + } +} + func TestIsValidGitBranchName(t *testing.T) { tests := []struct { name string @@ -3264,3 +3402,1382 @@ func TestGitHubRepository_Webhook(t *testing.T) { }) } } + +func TestGitHubRepository_LatestRef(t *testing.T) { + tests := []struct { + name string + setupMock func(mock *pgh.MockClient) + expectedRef string + expectedError error + }{ + { + name: "successful retrieval of latest ref", + setupMock: func(m *pgh.MockClient) { + m.On("GetBranch", mock.Anything, "grafana", "grafana", "main"). + Return(pgh.Branch{Sha: "abc123"}, nil) + }, + expectedRef: "abc123", + expectedError: nil, + }, + { + name: "error getting branch", + setupMock: func(m *pgh.MockClient) { + m.On("GetBranch", mock.Anything, "grafana", "grafana", "main"). + Return(pgh.Branch{}, fmt.Errorf("branch not found")) + }, + expectedRef: "", + expectedError: fmt.Errorf("get branch: branch not found"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup mock GitHub client + mockGH := pgh.NewMockClient(t) + tt.setupMock(mockGH) + + // Create repository with mock + repo := &githubRepository{ + gh: mockGH, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + Branch: "main", + }, + }, + }, + owner: "grafana", + repo: "grafana", + } + + // Call the LatestRef method + ref, err := repo.LatestRef(context.Background()) + + // Check results + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + require.Equal(t, tt.expectedRef, ref) + } + + // Verify all mock expectations were met + mockGH.AssertExpectations(t) + }) + } +} + +func TestGitHubRepository_CompareFiles(t *testing.T) { + tests := []struct { + name string + setupMock func(m *pgh.MockClient) + base string + ref string + expectedFiles []VersionedFileChange + expectedError error + shouldGetLatest bool + }{ + { + name: "successfully compare files", + setupMock: func(m *pgh.MockClient) { + commitFile1 := pgh.NewMockCommitFile(t) + commitFile1.On("GetFilename").Return("dashboards/test.json") + commitFile1.On("GetStatus").Return("added") + + commitFile2 := pgh.NewMockCommitFile(t) + commitFile2.On("GetFilename").Return("dashboards/modified.json") + commitFile2.On("GetStatus").Return("modified") + + commitFile3 := pgh.NewMockCommitFile(t) + commitFile3.On("GetFilename").Return("dashboards/renamed.json") + commitFile3.On("GetStatus").Return("renamed") + commitFile3.On("GetPreviousFilename").Return("dashboards/old.json") + + m.On("CompareCommits", mock.Anything, "grafana", "grafana", "abc123", "def456"). + Return([]pgh.CommitFile{ + commitFile1, + commitFile2, + commitFile3, + }, nil) + }, + base: "abc123", + ref: "def456", + expectedFiles: []VersionedFileChange{ + { + Path: "test.json", + Ref: "def456", + Action: FileActionCreated, + }, + { + Path: "modified.json", + Ref: "def456", + Action: FileActionUpdated, + }, + { + Path: "renamed.json", + Ref: "def456", + Action: FileActionRenamed, + PreviousPath: "old.json", + }, + }, + expectedError: nil, + }, + { + name: "error comparing commits", + setupMock: func(m *pgh.MockClient) { + m.On("CompareCommits", mock.Anything, "grafana", "grafana", "abc123", "def456"). + Return(nil, fmt.Errorf("failed to compare commits")) + }, + base: "abc123", + ref: "def456", + expectedFiles: nil, + expectedError: fmt.Errorf("compare commits: failed to compare commits"), + }, + { + name: "file outside configured path", + setupMock: func(m *pgh.MockClient) { + commitFile1 := pgh.NewMockCommitFile(t) + commitFile1.On("GetFilename").Return("../outside/path.json") + commitFile1.On("GetStatus").Return("added") + + commitFile2 := pgh.NewMockCommitFile(t) + commitFile2.On("GetFilename").Return("dashboards/valid.json") + commitFile2.On("GetStatus").Return("added") + + m.On("CompareCommits", mock.Anything, "grafana", "grafana", "abc123", "def456"). + Return([]pgh.CommitFile{ + commitFile1, + commitFile2, + }, nil) + }, + base: "abc123", + ref: "def456", + expectedFiles: []VersionedFileChange{ + { + Path: "valid.json", + Ref: "def456", + Action: FileActionCreated, + }, + }, + expectedError: nil, + }, + { + name: "modified file outside configured path", + setupMock: func(m *pgh.MockClient) { + commitFile1 := pgh.NewMockCommitFile(t) + commitFile1.On("GetFilename").Return("../outside/modified.json") + commitFile1.On("GetStatus").Return("modified") + + m.On("CompareCommits", mock.Anything, "grafana", "grafana", "abc123", "def456"). + Return([]pgh.CommitFile{ + commitFile1, + }, nil) + }, + base: "abc123", + ref: "def456", + expectedFiles: []VersionedFileChange{}, + expectedError: nil, + }, + { + name: "copied file status", + setupMock: func(m *pgh.MockClient) { + // File inside configured path + commitFile1 := pgh.NewMockCommitFile(t) + commitFile1.On("GetFilename").Return("dashboards/copied.json") + commitFile1.On("GetStatus").Return("copied") + + // File outside configured path + commitFile2 := pgh.NewMockCommitFile(t) + commitFile2.On("GetFilename").Return("../outside/copied.json") + commitFile2.On("GetStatus").Return("copied") + + m.On("CompareCommits", mock.Anything, "grafana", "grafana", "abc123", "def456"). + Return([]pgh.CommitFile{ + commitFile1, + commitFile2, + }, nil) + }, + base: "abc123", + ref: "def456", + expectedFiles: []VersionedFileChange{ + { + Path: "copied.json", + Ref: "def456", + Action: FileActionCreated, + }, + }, + expectedError: nil, + }, + { + name: "removed file status - inside path", + setupMock: func(m *pgh.MockClient) { + commitFile1 := pgh.NewMockCommitFile(t) + commitFile1.On("GetFilename").Return("dashboards/removed.json") + commitFile1.On("GetStatus").Return("removed") + + m.On("CompareCommits", mock.Anything, "grafana", "grafana", "abc123", "def456"). + Return([]pgh.CommitFile{ + commitFile1, + }, nil) + }, + base: "abc123", + ref: "def456", + expectedFiles: []VersionedFileChange{ + { + Path: "removed.json", + PreviousPath: "removed.json", + Ref: "def456", + PreviousRef: "abc123", + Action: FileActionDeleted, + }, + }, + expectedError: nil, + }, + { + name: "renamed file status - both paths outside configured path", + setupMock: func(m *pgh.MockClient) { + commitFile1 := pgh.NewMockCommitFile(t) + commitFile1.On("GetFilename").Return("../outside/renamed.json") + commitFile1.On("GetPreviousFilename").Return("../outside/original.json") + commitFile1.On("GetStatus").Return("renamed") + + m.On("CompareCommits", mock.Anything, "grafana", "grafana", "abc123", "def456"). + Return([]pgh.CommitFile{ + commitFile1, + }, nil) + }, + base: "abc123", + ref: "def456", + expectedFiles: []VersionedFileChange{}, + expectedError: nil, + }, + { + name: "renamed file status - both paths inside configured path", + setupMock: func(m *pgh.MockClient) { + commitFile1 := pgh.NewMockCommitFile(t) + commitFile1.On("GetFilename").Return("dashboards/renamed.json") + commitFile1.On("GetPreviousFilename").Return("dashboards/original.json") + commitFile1.On("GetStatus").Return("renamed") + + m.On("CompareCommits", mock.Anything, "grafana", "grafana", "abc123", "def456"). + Return([]pgh.CommitFile{ + commitFile1, + }, nil) + }, + base: "abc123", + ref: "def456", + expectedFiles: []VersionedFileChange{ + { + Path: "renamed.json", + PreviousPath: "original.json", + Ref: "def456", + PreviousRef: "abc123", + Action: FileActionRenamed, + }, + }, + expectedError: nil, + }, + { + name: "renamed file status - moving out of configured path", + setupMock: func(m *pgh.MockClient) { + commitFile1 := pgh.NewMockCommitFile(t) + commitFile1.On("GetFilename").Return("../outside/renamed.json") + commitFile1.On("GetPreviousFilename").Return("dashboards/original.json") + commitFile1.On("GetStatus").Return("renamed") + + m.On("CompareCommits", mock.Anything, "grafana", "grafana", "abc123", "def456"). + Return([]pgh.CommitFile{ + commitFile1, + }, nil) + }, + base: "abc123", + ref: "def456", + expectedFiles: []VersionedFileChange{ + { + Path: "original.json", + Ref: "abc123", + Action: FileActionDeleted, + }, + }, + expectedError: nil, + }, + { + name: "renamed file status - moving into configured path", + setupMock: func(m *pgh.MockClient) { + commitFile1 := pgh.NewMockCommitFile(t) + commitFile1.On("GetFilename").Return("dashboards/renamed.json") + commitFile1.On("GetPreviousFilename").Return("../outside/original.json") + commitFile1.On("GetStatus").Return("renamed") + + m.On("CompareCommits", mock.Anything, "grafana", "grafana", "abc123", "def456"). + Return([]pgh.CommitFile{ + commitFile1, + }, nil) + }, + base: "abc123", + ref: "def456", + expectedFiles: []VersionedFileChange{ + { + Path: "renamed.json", + Ref: "def456", + Action: FileActionCreated, + }, + }, + expectedError: nil, + }, + { + name: "removed file status - outside path", + setupMock: func(m *pgh.MockClient) { + commitFile1 := pgh.NewMockCommitFile(t) + commitFile1.On("GetFilename").Return("../outside/removed.json") + commitFile1.On("GetStatus").Return("removed") + + m.On("CompareCommits", mock.Anything, "grafana", "grafana", "abc123", "def456"). + Return([]pgh.CommitFile{ + commitFile1, + }, nil) + }, + base: "abc123", + ref: "def456", + expectedFiles: []VersionedFileChange{}, + expectedError: nil, + }, + { + name: "changed file outside configured path", + setupMock: func(m *pgh.MockClient) { + commitFile1 := pgh.NewMockCommitFile(t) + commitFile1.On("GetFilename").Return("../outside/changed.json") + commitFile1.On("GetStatus").Return("changed") + + m.On("CompareCommits", mock.Anything, "grafana", "grafana", "abc123", "def456"). + Return([]pgh.CommitFile{ + commitFile1, + }, nil) + }, + base: "abc123", + ref: "def456", + expectedFiles: []VersionedFileChange{}, + expectedError: nil, + }, + { + name: "get latest ref when ref is empty", + setupMock: func(m *pgh.MockClient) { + commitFile1 := pgh.NewMockCommitFile(t) + commitFile1.On("GetFilename").Return("dashboards/test.json") + commitFile1.On("GetStatus").Return("added") + + m.On("GetBranch", mock.Anything, "grafana", "grafana", "main"). + Return(pgh.Branch{Sha: "latest123"}, nil) + m.On("CompareCommits", mock.Anything, "grafana", "grafana", "abc123", "latest123"). + Return([]pgh.CommitFile{commitFile1}, nil) + }, + base: "abc123", + ref: "", + shouldGetLatest: true, + expectedFiles: []VersionedFileChange{ + { + Path: "test.json", + Ref: "latest123", + Action: FileActionCreated, + }, + }, + expectedError: nil, + }, + { + name: "unchanged file status", + setupMock: func(m *pgh.MockClient) { + commitFile1 := pgh.NewMockCommitFile(t) + commitFile1.On("GetStatus").Return("unchanged") + + m.On("CompareCommits", mock.Anything, "grafana", "grafana", "abc123", "def456"). + Return([]pgh.CommitFile{ + commitFile1, + }, nil) + }, + base: "abc123", + ref: "def456", + expectedFiles: []VersionedFileChange{}, + expectedError: nil, + }, + { + name: "unknown file status", + setupMock: func(m *pgh.MockClient) { + commitFile1 := pgh.NewMockCommitFile(t) + commitFile1.On("GetFilename").Return("dashboards/unknown.json") + commitFile1.On("GetStatus").Return("unknown_status") + + m.On("CompareCommits", mock.Anything, "grafana", "grafana", "abc123", "def456"). + Return([]pgh.CommitFile{ + commitFile1, + }, nil) + }, + base: "abc123", + ref: "def456", + expectedFiles: []VersionedFileChange{}, + expectedError: nil, + }, + { + name: "error getting latest ref", + setupMock: func(m *pgh.MockClient) { + m.On("GetBranch", mock.Anything, "grafana", "grafana", "main"). + Return(pgh.Branch{}, fmt.Errorf("branch not found")) + }, + base: "abc123", + ref: "", + shouldGetLatest: true, + expectedFiles: nil, + expectedError: fmt.Errorf("get latest ref: get branch: branch not found"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup mock GitHub client + mockGH := pgh.NewMockClient(t) + tt.setupMock(mockGH) + + // Create repository with mock + repo := &githubRepository{ + gh: mockGH, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + Branch: "main", + Path: "dashboards", + }, + }, + }, + owner: "grafana", + repo: "grafana", + } + + // Call the CompareFiles method + files, err := repo.CompareFiles(context.Background(), tt.base, tt.ref) + + // Check results + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + require.Equal(t, len(tt.expectedFiles), len(files)) + + for i, expectedFile := range tt.expectedFiles { + require.Equal(t, expectedFile.Path, files[i].Path) + require.Equal(t, expectedFile.Ref, files[i].Ref) + require.Equal(t, expectedFile.Action, files[i].Action) + require.Equal(t, expectedFile.PreviousPath, files[i].PreviousPath) + } + } + + // Verify all mock expectations were met + mockGH.AssertExpectations(t) + }) + } +} + +func TestGitHubRepository_CommentPullRequest(t *testing.T) { + tests := []struct { + name string + setupMock func(m *pgh.MockClient) + prNumber int + comment string + expectedError error + }{ + { + name: "successfully comment on pull request", + setupMock: func(m *pgh.MockClient) { + m.On("CreatePullRequestComment", mock.Anything, "grafana", "grafana", 123, "Test comment"). + Return(nil) + }, + prNumber: 123, + comment: "Test comment", + expectedError: nil, + }, + { + name: "error commenting on pull request", + setupMock: func(m *pgh.MockClient) { + m.On("CreatePullRequestComment", mock.Anything, "grafana", "grafana", 456, "Error comment"). + Return(fmt.Errorf("failed to create comment")) + }, + prNumber: 456, + comment: "Error comment", + expectedError: fmt.Errorf("failed to create comment"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup mock GitHub client + mockGH := pgh.NewMockClient(t) + tt.setupMock(mockGH) + + // Create repository with mock + repo := &githubRepository{ + gh: mockGH, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + Branch: "main", + }, + }, + }, + owner: "grafana", + repo: "grafana", + } + + // Call the CommentPullRequest method + err := repo.CommentPullRequest(context.Background(), tt.prNumber, tt.comment) + + // Check results + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + } + + // Verify all mock expectations were met + mockGH.AssertExpectations(t) + }) + } +} + +func TestGitHubRepository_ResourceURLs(t *testing.T) { + tests := []struct { + name string + file *FileInfo + config *provisioning.Repository + expectedURLs *provisioning.ResourceURLs + expectedError error + }{ + { + name: "file with ref", + file: &FileInfo{ + Path: "dashboards/test.json", + Ref: "feature-branch", + }, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + URL: "https://github.com/grafana/grafana", + Branch: "main", + }, + }, + }, + expectedURLs: &provisioning.ResourceURLs{ + RepositoryURL: "https://github.com/grafana/grafana", + SourceURL: "https://github.com/grafana/grafana/blob/feature-branch/dashboards/test.json", + CompareURL: "https://github.com/grafana/grafana/compare/main...feature-branch", + NewPullRequestURL: "https://github.com/grafana/grafana/compare/main...feature-branch?quick_pull=1&labels=grafana", + }, + expectedError: nil, + }, + { + name: "file without ref uses default branch", + file: &FileInfo{ + Path: "dashboards/test.json", + Ref: "", + }, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + URL: "https://github.com/grafana/grafana", + Branch: "main", + }, + }, + }, + expectedURLs: &provisioning.ResourceURLs{ + RepositoryURL: "https://github.com/grafana/grafana", + SourceURL: "https://github.com/grafana/grafana/blob/main/dashboards/test.json", + }, + expectedError: nil, + }, + { + name: "file with ref same as branch", + file: &FileInfo{ + Path: "dashboards/test.json", + Ref: "main", + }, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + URL: "https://github.com/grafana/grafana", + Branch: "main", + }, + }, + }, + expectedURLs: &provisioning.ResourceURLs{ + RepositoryURL: "https://github.com/grafana/grafana", + SourceURL: "https://github.com/grafana/grafana/blob/main/dashboards/test.json", + }, + expectedError: nil, + }, + { + name: "empty path returns nil", + file: &FileInfo{ + Path: "", + Ref: "feature-branch", + }, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + URL: "https://github.com/grafana/grafana", + Branch: "main", + }, + }, + }, + expectedURLs: nil, + expectedError: nil, + }, + { + name: "nil github config returns nil", + file: &FileInfo{ + Path: "dashboards/test.json", + Ref: "feature-branch", + }, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: nil, + }, + }, + expectedURLs: nil, + expectedError: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create repository + repo := &githubRepository{ + config: tt.config, + owner: "grafana", + repo: "grafana", + } + + // Call the ResourceURLs method + urls, err := repo.ResourceURLs(context.Background(), tt.file) + + // Check results + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + require.Equal(t, tt.expectedURLs, urls) + } + }) + } +} + +func TestGitHubRepository_OnCreate(t *testing.T) { + tests := []struct { + name string + setupMock func(m *pgh.MockClient) + config *provisioning.Repository + webhookURL string + expectedHook *provisioning.WebhookStatus + expectedError error + }{ + { + name: "successfully create webhook", + setupMock: func(m *pgh.MockClient) { + m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.MatchedBy(func(cfg pgh.WebhookConfig) bool { + return cfg.URL == "https://example.com/webhook" && + cfg.ContentType == "json" && + cfg.Active == true + })).Return(pgh.WebhookConfig{ + ID: 123, + URL: "https://example.com/webhook", + Secret: "test-secret", + }, nil) + }, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + Branch: "main", + }, + }, + }, + webhookURL: "https://example.com/webhook", + expectedHook: &provisioning.WebhookStatus{ + ID: 123, + URL: "https://example.com/webhook", + Secret: "test-secret", + }, + expectedError: nil, + }, + { + name: "no webhook URL", + setupMock: func(m *pgh.MockClient) { + // No webhook creation expected + }, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + Branch: "main", + }, + }, + }, + webhookURL: "", + expectedHook: nil, + expectedError: nil, + }, + { + name: "error creating webhook", + setupMock: func(m *pgh.MockClient) { + m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.Anything). + Return(pgh.WebhookConfig{}, fmt.Errorf("failed to create webhook")) + }, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + Branch: "main", + }, + }, + }, + webhookURL: "https://example.com/webhook", + expectedHook: nil, + expectedError: fmt.Errorf("failed to create webhook"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup mock GitHub client + mockGH := pgh.NewMockClient(t) + tt.setupMock(mockGH) + + // Create repository with mock + repo := &githubRepository{ + gh: mockGH, + config: tt.config, + owner: "grafana", + repo: "grafana", + webhookURL: tt.webhookURL, + } + + // Call the OnCreate method + hook, err := repo.OnCreate(context.Background()) + + // Check results + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + require.Nil(t, hook) + } else { + require.NoError(t, err) + if tt.expectedHook != nil { + require.NotNil(t, hook) + require.Equal(t, tt.expectedHook.ID, hook.ID) + require.Equal(t, tt.expectedHook.URL, hook.URL) + require.NotEmpty(t, hook.Secret) // Secret is randomly generated, so just check it's not empty + } else { + require.Nil(t, hook) + } + } + + // Verify all mock expectations were met + mockGH.AssertExpectations(t) + }) + } +} + +func TestGitHubRepository_OnUpdate(t *testing.T) { + tests := []struct { + name string + setupMock func(m *pgh.MockClient) + config *provisioning.Repository + webhookURL string + expectedHook *provisioning.WebhookStatus + expectedError error + }{ + { + name: "successfully update webhook when webhook exists", + setupMock: func(m *pgh.MockClient) { + // Mock getting the existing webhook + m.On("GetWebhook", mock.Anything, "grafana", "grafana", int64(123)). + Return(pgh.WebhookConfig{ + ID: 123, + URL: "https://example.com/webhook", + Events: []string{"push"}, + }, nil) + + // Mock editing the webhook + m.On("EditWebhook", mock.Anything, "grafana", "grafana", mock.MatchedBy(func(hook pgh.WebhookConfig) bool { + return hook.ID == 123 && hook.URL == "https://example.com/webhook-updated" && + slices.Equal(hook.Events, subscribedEvents) + })).Return(nil) + }, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + Branch: "main", + }, + }, + Status: provisioning.RepositoryStatus{ + Webhook: &provisioning.WebhookStatus{ + ID: 123, + URL: "https://example.com/webhook", + }, + }, + }, + webhookURL: "https://example.com/webhook-updated", + expectedHook: &provisioning.WebhookStatus{ + ID: 123, + URL: "https://example.com/webhook-updated", + SubscribedEvents: subscribedEvents, + }, + expectedError: nil, + }, + { + name: "create webhook when it doesn't exist", + setupMock: func(m *pgh.MockClient) { + // Mock webhook not found + m.On("GetWebhook", mock.Anything, "grafana", "grafana", int64(123)). + Return(pgh.WebhookConfig{}, pgh.ErrResourceNotFound) + + // Mock creating a new webhook + m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.MatchedBy(func(hook pgh.WebhookConfig) bool { + return hook.URL == "https://example.com/webhook" && + hook.ContentType == "json" && + slices.Equal(hook.Events, subscribedEvents) && + hook.Active == true + })).Return(pgh.WebhookConfig{ + ID: 456, + URL: "https://example.com/webhook", + Events: subscribedEvents, + }, nil) + }, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + Branch: "main", + }, + }, + Status: provisioning.RepositoryStatus{ + Webhook: &provisioning.WebhookStatus{ + ID: 123, + URL: "https://example.com/old-webhook", + }, + }, + }, + webhookURL: "https://example.com/webhook", + expectedHook: &provisioning.WebhookStatus{ + ID: 456, + URL: "https://example.com/webhook", + SubscribedEvents: subscribedEvents, + }, + expectedError: nil, + }, + { + name: "no webhook URL provided", + setupMock: func(m *pgh.MockClient) { + // No mocks needed + }, + config: &provisioning.Repository{}, + webhookURL: "", + expectedHook: nil, + expectedError: nil, + }, + { + name: "error getting webhook", + setupMock: func(m *pgh.MockClient) { + m.On("GetWebhook", mock.Anything, "grafana", "grafana", int64(123)). + Return(pgh.WebhookConfig{}, fmt.Errorf("failed to get webhook")) + }, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + Branch: "main", + }, + }, + Status: provisioning.RepositoryStatus{ + Webhook: &provisioning.WebhookStatus{ + ID: 123, + URL: "https://example.com/webhook", + }, + }, + }, + webhookURL: "https://example.com/webhook", + expectedHook: nil, + expectedError: fmt.Errorf("get webhook: failed to get webhook"), + }, + { + name: "error editing webhook", + setupMock: func(m *pgh.MockClient) { + // Mock getting the existing webhook + m.On("GetWebhook", mock.Anything, "grafana", "grafana", int64(123)). + Return(pgh.WebhookConfig{ + ID: 123, + URL: "https://example.com/webhook", + Events: []string{"push"}, + }, nil) + + // Mock editing the webhook with error + m.On("EditWebhook", mock.Anything, "grafana", "grafana", mock.Anything). + Return(fmt.Errorf("failed to edit webhook")) + }, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + Branch: "main", + }, + }, + Status: provisioning.RepositoryStatus{ + Webhook: &provisioning.WebhookStatus{ + ID: 123, + URL: "https://example.com/webhook", + }, + }, + }, + webhookURL: "https://example.com/webhook-updated", + expectedHook: nil, + expectedError: fmt.Errorf("edit webhook: failed to edit webhook"), + }, + { + name: "create webhook when webhook status is nil", + setupMock: func(m *pgh.MockClient) { + // Mock creating a new webhook + m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.Anything). + Return(pgh.WebhookConfig{ + ID: 456, + URL: "https://example.com/webhook", + Events: subscribedEvents, + Active: true, + ContentType: "json", + }, nil) + }, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + Branch: "main", + }, + }, + Status: provisioning.RepositoryStatus{ + Webhook: nil, // Webhook status is nil + }, + }, + webhookURL: "https://example.com/webhook", + expectedHook: &provisioning.WebhookStatus{ + ID: 456, + URL: "https://example.com/webhook", + SubscribedEvents: subscribedEvents, + }, + expectedError: nil, + }, + { + name: "create webhook when webhook ID is zero", + setupMock: func(m *pgh.MockClient) { + // Mock creating a new webhook + m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.Anything). + Return(pgh.WebhookConfig{ + ID: 789, + URL: "https://example.com/webhook", + Events: subscribedEvents, + Active: true, + ContentType: "json", + }, nil) + }, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + Branch: "main", + }, + }, + Status: provisioning.RepositoryStatus{ + Webhook: &provisioning.WebhookStatus{ + ID: 0, // Webhook ID is zero + URL: "https://example.com/webhook", + }, + }, + }, + webhookURL: "https://example.com/webhook", + expectedHook: &provisioning.WebhookStatus{ + ID: 789, + URL: "https://example.com/webhook", + SubscribedEvents: subscribedEvents, + }, + expectedError: nil, + }, + { + name: "error when creating webhook fails", + setupMock: func(m *pgh.MockClient) { + // Mock webhook creation failure + m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.Anything). + Return(pgh.WebhookConfig{}, fmt.Errorf("failed to create webhook")) + }, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + Branch: "main", + }, + }, + Status: provisioning.RepositoryStatus{ + Webhook: nil, // Webhook status is nil + }, + }, + webhookURL: "https://example.com/webhook", + expectedHook: nil, + expectedError: fmt.Errorf("failed to create webhook"), + }, + { + name: "creates webhook when ErrResourceNotFound", + setupMock: func(m *pgh.MockClient) { + // Mock webhook not found + m.On("GetWebhook", mock.Anything, "grafana", "grafana", int64(123)). + Return(pgh.WebhookConfig{}, pgh.ErrResourceNotFound) + + // Mock creating a new webhook + m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.MatchedBy(func(hook pgh.WebhookConfig) bool { + return hook.URL == "https://example.com/webhook" && + hook.ContentType == "json" && + slices.Equal(hook.Events, subscribedEvents) && + hook.Active == true + })).Return(pgh.WebhookConfig{ + ID: 456, + URL: "https://example.com/webhook", + Events: subscribedEvents, + }, nil) + }, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + Branch: "main", + }, + }, + Status: provisioning.RepositoryStatus{ + Webhook: &provisioning.WebhookStatus{ + ID: 123, + URL: "https://example.com/old-webhook", + }, + }, + }, + webhookURL: "https://example.com/webhook", + expectedHook: &provisioning.WebhookStatus{ + ID: 456, + URL: "https://example.com/webhook", + SubscribedEvents: subscribedEvents, + }, + expectedError: nil, + }, + { + name: "error on create when not found", + setupMock: func(m *pgh.MockClient) { + // Mock webhook not found + m.On("GetWebhook", mock.Anything, "grafana", "grafana", int64(123)). + Return(pgh.WebhookConfig{}, pgh.ErrResourceNotFound) + + // Mock error when creating a new webhook + m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.MatchedBy(func(hook pgh.WebhookConfig) bool { + return hook.URL == "https://example.com/webhook" && + hook.ContentType == "json" && + slices.Equal(hook.Events, subscribedEvents) && + hook.Active == true + })).Return(pgh.WebhookConfig{}, fmt.Errorf("failed to create webhook")) + }, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + Branch: "main", + }, + }, + Status: provisioning.RepositoryStatus{ + Webhook: &provisioning.WebhookStatus{ + ID: 123, + URL: "https://example.com/old-webhook", + }, + }, + }, + webhookURL: "https://example.com/webhook", + expectedHook: nil, + expectedError: fmt.Errorf("failed to create webhook"), + }, + { + name: "no update needed when URL and events match", + setupMock: func(m *pgh.MockClient) { + // Mock getting the existing webhook with matching URL and events + m.On("GetWebhook", mock.Anything, "grafana", "grafana", int64(123)). + Return(pgh.WebhookConfig{ + ID: 123, + URL: "https://example.com/webhook", + Events: subscribedEvents, + }, nil) + + // No EditWebhook call expected since no changes needed + }, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + Branch: "main", + }, + }, + Status: provisioning.RepositoryStatus{ + Webhook: &provisioning.WebhookStatus{ + ID: 123, + URL: "https://example.com/webhook", + Secret: "secret", + }, + }, + }, + webhookURL: "https://example.com/webhook", + expectedHook: &provisioning.WebhookStatus{ + ID: 123, + URL: "https://example.com/webhook", + SubscribedEvents: subscribedEvents, + Secret: "secret", + }, + expectedError: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup mock GitHub client + mockGH := pgh.NewMockClient(t) + tt.setupMock(mockGH) + + // Create repository with mock + repo := &githubRepository{ + gh: mockGH, + config: tt.config, + owner: "grafana", + repo: "grafana", + webhookURL: tt.webhookURL, + } + + // Call the OnUpdate method + hook, err := repo.OnUpdate(context.Background()) + + // Check results + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + require.Nil(t, hook) + } else { + require.NoError(t, err) + if tt.expectedHook != nil { + require.NotNil(t, hook) + require.Equal(t, tt.expectedHook.ID, hook.ID) + require.Equal(t, tt.expectedHook.URL, hook.URL) + if tt.expectedHook.Secret != "" { + require.Equal(t, tt.expectedHook.Secret, hook.Secret) + } else { + require.NotEmpty(t, hook.Secret) // Secret is randomly generated, so just check it's not empty + } + require.ElementsMatch(t, tt.expectedHook.SubscribedEvents, hook.SubscribedEvents) + } else { + require.Nil(t, hook) + } + } + + // Verify all mock expectations were met + mockGH.AssertExpectations(t) + }) + } +} + +func TestGitHubRepository_OnDelete(t *testing.T) { + tests := []struct { + name string + setupMock func(m *pgh.MockClient) + config *provisioning.Repository + webhookURL string + expectedError error + }{ + { + name: "successfully delete webhook", + setupMock: func(m *pgh.MockClient) { + // Mock deleting the webhook + m.On("DeleteWebhook", mock.Anything, "grafana", "grafana", int64(123)). + Return(nil) + }, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + Branch: "main", + }, + }, + Status: provisioning.RepositoryStatus{ + Webhook: &provisioning.WebhookStatus{ + ID: 123, + URL: "https://example.com/webhook", + }, + }, + }, + webhookURL: "https://example.com/webhook", + expectedError: nil, + }, + { + name: "no webhook URL provided", + setupMock: func(m *pgh.MockClient) { + // No mocks needed + }, + config: &provisioning.Repository{}, + webhookURL: "", + expectedError: nil, + }, + { + name: "webhook not found in status", + setupMock: func(m *pgh.MockClient) { + // No mocks needed + }, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + Branch: "main", + }, + }, + Status: provisioning.RepositoryStatus{ + Webhook: nil, // Webhook status is nil + }, + }, + webhookURL: "https://example.com/webhook", + expectedError: fmt.Errorf("webhook not found"), + }, + { + name: "error deleting webhook", + setupMock: func(m *pgh.MockClient) { + // Mock webhook deletion failure + m.On("DeleteWebhook", mock.Anything, "grafana", "grafana", int64(123)). + Return(fmt.Errorf("failed to delete webhook")) + }, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + Branch: "main", + }, + }, + Status: provisioning.RepositoryStatus{ + Webhook: &provisioning.WebhookStatus{ + ID: 123, + URL: "https://example.com/webhook", + }, + }, + }, + webhookURL: "https://example.com/webhook", + expectedError: fmt.Errorf("delete webhook: failed to delete webhook"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup mock GitHub client + mockGH := pgh.NewMockClient(t) + tt.setupMock(mockGH) + + // Create repository with mock + repo := &githubRepository{ + gh: mockGH, + config: tt.config, + owner: "grafana", + repo: "grafana", + webhookURL: tt.webhookURL, + } + + // Call the OnDelete method + err := repo.OnDelete(context.Background()) + + // Check results + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + } + + // Verify all mock expectations were met + mockGH.AssertExpectations(t) + }) + } +} + +func TestGitHubRepository_Clone(t *testing.T) { + tests := []struct { + name string + setupMock func(m *MockCloneFn) + config *provisioning.Repository + expectedError error + }{ + { + name: "successfully clone repository", + setupMock: func(m *MockCloneFn) { + m.On("Execute", mock.Anything, CloneOptions{ + CreateIfNotExists: true, + PushOnWrites: true, + MaxSize: 1024 * 1024 * 10, // 10MB + Timeout: 10 * time.Second, + Progress: io.Discard, + BeforeFn: nil, + }).Return(nil, nil) + }, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + Branch: "main", + }, + }, + }, + expectedError: nil, + }, + { + name: "error cloning repository", + setupMock: func(m *MockCloneFn) { + m.On("Execute", mock.Anything, CloneOptions{ + CreateIfNotExists: true, + PushOnWrites: true, + MaxSize: 1024 * 1024 * 10, // 10MB + Timeout: 10 * time.Second, + Progress: io.Discard, + BeforeFn: nil, + }).Return(nil, fmt.Errorf("failed to clone repository")) + }, + config: &provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + Branch: "main", + }, + }, + }, + expectedError: fmt.Errorf("failed to clone repository"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockCloneFn := NewMockCloneFn(t) + + tt.setupMock(mockCloneFn) + + // Create repository with mock + repo := &githubRepository{ + cloneFn: mockCloneFn.Execute, + config: tt.config, + owner: "grafana", + repo: "grafana", + } + + // Call the Clone method with a placeholder directory path + _, err := repo.Clone(context.Background(), CloneOptions{ + CreateIfNotExists: true, + PushOnWrites: true, + MaxSize: 1024 * 1024 * 10, // 10MB + Timeout: 10 * time.Second, + Progress: io.Discard, + BeforeFn: nil, + }) + + // Check results + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + } else { + require.NoError(t, err) + } + + // Verify all mock expectations were met + mockCloneFn.AssertExpectations(t) + }) + } +} diff --git a/pkg/registry/apis/provisioning/repository/repository.go b/pkg/registry/apis/provisioning/repository/repository.go index 05c8e74ee6e..88034d81335 100644 --- a/pkg/registry/apis/provisioning/repository/repository.go +++ b/pkg/registry/apis/provisioning/repository/repository.go @@ -53,6 +53,7 @@ type FileInfo struct { Modified *metav1.Time } +//go:generate mockery --name CloneFn --structname MockCloneFn --inpackage --filename clone_fn_mock.go --with-expecter type CloneFn func(ctx context.Context, opts CloneOptions) (ClonedRepository, error) type CloneOptions struct { From 9e9e971ab3f87f33d597e9f6348617603a54cba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Jim=C3=A9nez=20S=C3=A1nchez?= Date: Wed, 23 Apr 2025 14:59:03 +0200 Subject: [PATCH 052/146] Provisioning: unit test and bug fixes go-git repository (#104390) * Add unit test for unimplemented methods * Add unit test for GoGitRepo_Read * Add tests for Delete * Add more tests * Add unit test for GoGitRepo_Push * Add unit test for ReadTree --- .../repository/go-git/repository_mock.go | 84 ++ .../repository/go-git/worktree_mock.go | 261 ++++ .../provisioning/repository/go-git/wrapper.go | 66 +- .../repository/go-git/wrapper_test.go | 1230 ++++++++++++++++- 4 files changed, 1620 insertions(+), 21 deletions(-) create mode 100644 pkg/registry/apis/provisioning/repository/go-git/repository_mock.go create mode 100644 pkg/registry/apis/provisioning/repository/go-git/worktree_mock.go diff --git a/pkg/registry/apis/provisioning/repository/go-git/repository_mock.go b/pkg/registry/apis/provisioning/repository/go-git/repository_mock.go new file mode 100644 index 00000000000..da1f2a005cf --- /dev/null +++ b/pkg/registry/apis/provisioning/repository/go-git/repository_mock.go @@ -0,0 +1,84 @@ +// Code generated by mockery v2.52.4. DO NOT EDIT. + +package gogit + +import ( + context "context" + + git "github.com/go-git/go-git/v5" + mock "github.com/stretchr/testify/mock" +) + +// MockRepository is an autogenerated mock type for the Repository type +type MockRepository struct { + mock.Mock +} + +type MockRepository_Expecter struct { + mock *mock.Mock +} + +func (_m *MockRepository) EXPECT() *MockRepository_Expecter { + return &MockRepository_Expecter{mock: &_m.Mock} +} + +// PushContext provides a mock function with given fields: ctx, o +func (_m *MockRepository) PushContext(ctx context.Context, o *git.PushOptions) error { + ret := _m.Called(ctx, o) + + if len(ret) == 0 { + panic("no return value specified for PushContext") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *git.PushOptions) error); ok { + r0 = rf(ctx, o) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockRepository_PushContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PushContext' +type MockRepository_PushContext_Call struct { + *mock.Call +} + +// PushContext is a helper method to define mock.On call +// - ctx context.Context +// - o *git.PushOptions +func (_e *MockRepository_Expecter) PushContext(ctx interface{}, o interface{}) *MockRepository_PushContext_Call { + return &MockRepository_PushContext_Call{Call: _e.mock.On("PushContext", ctx, o)} +} + +func (_c *MockRepository_PushContext_Call) Run(run func(ctx context.Context, o *git.PushOptions)) *MockRepository_PushContext_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*git.PushOptions)) + }) + return _c +} + +func (_c *MockRepository_PushContext_Call) Return(_a0 error) *MockRepository_PushContext_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockRepository_PushContext_Call) RunAndReturn(run func(context.Context, *git.PushOptions) error) *MockRepository_PushContext_Call { + _c.Call.Return(run) + return _c +} + +// NewMockRepository creates a new instance of MockRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *MockRepository { + mock := &MockRepository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/registry/apis/provisioning/repository/go-git/worktree_mock.go b/pkg/registry/apis/provisioning/repository/go-git/worktree_mock.go new file mode 100644 index 00000000000..0a85a14e12b --- /dev/null +++ b/pkg/registry/apis/provisioning/repository/go-git/worktree_mock.go @@ -0,0 +1,261 @@ +// Code generated by mockery v2.52.4. DO NOT EDIT. + +package gogit + +import ( + billy "github.com/go-git/go-billy/v5" + git "github.com/go-git/go-git/v5" + + mock "github.com/stretchr/testify/mock" + + plumbing "github.com/go-git/go-git/v5/plumbing" +) + +// MockWorktree is an autogenerated mock type for the Worktree type +type MockWorktree struct { + mock.Mock +} + +type MockWorktree_Expecter struct { + mock *mock.Mock +} + +func (_m *MockWorktree) EXPECT() *MockWorktree_Expecter { + return &MockWorktree_Expecter{mock: &_m.Mock} +} + +// Add provides a mock function with given fields: path +func (_m *MockWorktree) Add(path string) (plumbing.Hash, error) { + ret := _m.Called(path) + + if len(ret) == 0 { + panic("no return value specified for Add") + } + + var r0 plumbing.Hash + var r1 error + if rf, ok := ret.Get(0).(func(string) (plumbing.Hash, error)); ok { + return rf(path) + } + if rf, ok := ret.Get(0).(func(string) plumbing.Hash); ok { + r0 = rf(path) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(plumbing.Hash) + } + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(path) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockWorktree_Add_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Add' +type MockWorktree_Add_Call struct { + *mock.Call +} + +// Add is a helper method to define mock.On call +// - path string +func (_e *MockWorktree_Expecter) Add(path interface{}) *MockWorktree_Add_Call { + return &MockWorktree_Add_Call{Call: _e.mock.On("Add", path)} +} + +func (_c *MockWorktree_Add_Call) Run(run func(path string)) *MockWorktree_Add_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *MockWorktree_Add_Call) Return(_a0 plumbing.Hash, _a1 error) *MockWorktree_Add_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockWorktree_Add_Call) RunAndReturn(run func(string) (plumbing.Hash, error)) *MockWorktree_Add_Call { + _c.Call.Return(run) + return _c +} + +// Commit provides a mock function with given fields: message, opts +func (_m *MockWorktree) Commit(message string, opts *git.CommitOptions) (plumbing.Hash, error) { + ret := _m.Called(message, opts) + + if len(ret) == 0 { + panic("no return value specified for Commit") + } + + var r0 plumbing.Hash + var r1 error + if rf, ok := ret.Get(0).(func(string, *git.CommitOptions) (plumbing.Hash, error)); ok { + return rf(message, opts) + } + if rf, ok := ret.Get(0).(func(string, *git.CommitOptions) plumbing.Hash); ok { + r0 = rf(message, opts) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(plumbing.Hash) + } + } + + if rf, ok := ret.Get(1).(func(string, *git.CommitOptions) error); ok { + r1 = rf(message, opts) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockWorktree_Commit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Commit' +type MockWorktree_Commit_Call struct { + *mock.Call +} + +// Commit is a helper method to define mock.On call +// - message string +// - opts *git.CommitOptions +func (_e *MockWorktree_Expecter) Commit(message interface{}, opts interface{}) *MockWorktree_Commit_Call { + return &MockWorktree_Commit_Call{Call: _e.mock.On("Commit", message, opts)} +} + +func (_c *MockWorktree_Commit_Call) Run(run func(message string, opts *git.CommitOptions)) *MockWorktree_Commit_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].(*git.CommitOptions)) + }) + return _c +} + +func (_c *MockWorktree_Commit_Call) Return(_a0 plumbing.Hash, _a1 error) *MockWorktree_Commit_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockWorktree_Commit_Call) RunAndReturn(run func(string, *git.CommitOptions) (plumbing.Hash, error)) *MockWorktree_Commit_Call { + _c.Call.Return(run) + return _c +} + +// Filesystem provides a mock function with no fields +func (_m *MockWorktree) Filesystem() billy.Filesystem { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Filesystem") + } + + var r0 billy.Filesystem + if rf, ok := ret.Get(0).(func() billy.Filesystem); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(billy.Filesystem) + } + } + + return r0 +} + +// MockWorktree_Filesystem_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Filesystem' +type MockWorktree_Filesystem_Call struct { + *mock.Call +} + +// Filesystem is a helper method to define mock.On call +func (_e *MockWorktree_Expecter) Filesystem() *MockWorktree_Filesystem_Call { + return &MockWorktree_Filesystem_Call{Call: _e.mock.On("Filesystem")} +} + +func (_c *MockWorktree_Filesystem_Call) Run(run func()) *MockWorktree_Filesystem_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockWorktree_Filesystem_Call) Return(_a0 billy.Filesystem) *MockWorktree_Filesystem_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockWorktree_Filesystem_Call) RunAndReturn(run func() billy.Filesystem) *MockWorktree_Filesystem_Call { + _c.Call.Return(run) + return _c +} + +// Remove provides a mock function with given fields: path +func (_m *MockWorktree) Remove(path string) (plumbing.Hash, error) { + ret := _m.Called(path) + + if len(ret) == 0 { + panic("no return value specified for Remove") + } + + var r0 plumbing.Hash + var r1 error + if rf, ok := ret.Get(0).(func(string) (plumbing.Hash, error)); ok { + return rf(path) + } + if rf, ok := ret.Get(0).(func(string) plumbing.Hash); ok { + r0 = rf(path) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(plumbing.Hash) + } + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(path) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockWorktree_Remove_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Remove' +type MockWorktree_Remove_Call struct { + *mock.Call +} + +// Remove is a helper method to define mock.On call +// - path string +func (_e *MockWorktree_Expecter) Remove(path interface{}) *MockWorktree_Remove_Call { + return &MockWorktree_Remove_Call{Call: _e.mock.On("Remove", path)} +} + +func (_c *MockWorktree_Remove_Call) Run(run func(path string)) *MockWorktree_Remove_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *MockWorktree_Remove_Call) Return(_a0 plumbing.Hash, _a1 error) *MockWorktree_Remove_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockWorktree_Remove_Call) RunAndReturn(run func(string) (plumbing.Hash, error)) *MockWorktree_Remove_Call { + _c.Call.Return(run) + return _c +} + +// NewMockWorktree creates a new instance of MockWorktree. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockWorktree(t interface { + mock.TestingT + Cleanup(func()) +}) *MockWorktree { + mock := &MockWorktree{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/registry/apis/provisioning/repository/go-git/wrapper.go b/pkg/registry/apis/provisioning/repository/go-git/wrapper.go index 7a722cee2ce..a6f9ecfd655 100644 --- a/pkg/registry/apis/provisioning/repository/go-git/wrapper.go +++ b/pkg/registry/apis/provisioning/repository/go-git/wrapper.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/util" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" @@ -43,6 +44,27 @@ func init() { client.InstallProtocol("http", httpClient) } +//go:generate mockery --name=Worktree --output=mocks --inpackage --filename=worktree_mock.go --with-expecter +type Worktree interface { + Commit(message string, opts *git.CommitOptions) (plumbing.Hash, error) + Remove(path string) (plumbing.Hash, error) + Add(path string) (plumbing.Hash, error) + Filesystem() billy.Filesystem +} + +type worktree struct { + *git.Worktree +} + +//go:generate mockery --name=Repository --output=mocks --inpackage --filename=repository_mock.go --with-expecter +type Repository interface { + PushContext(ctx context.Context, o *git.PushOptions) error +} + +func (w *worktree) Filesystem() billy.Filesystem { + return w.Worktree.Filesystem +} + var _ repository.Repository = (*GoGitRepo)(nil) type GoGitRepo struct { @@ -50,8 +72,8 @@ type GoGitRepo struct { decryptedPassword string opts repository.CloneOptions - repo *git.Repository - tree *git.Worktree + repo Repository + tree Worktree dir string // file path to worktree root (necessary? should use billy) } @@ -101,7 +123,7 @@ func Clone( progress = io.Discard } - repo, worktree, err := clone(ctx, config, opts, decrypted, dir, progress) + repo, tree, err := clone(ctx, config, opts, decrypted, dir, progress) if err != nil { if err := os.RemoveAll(dir); err != nil { return nil, fmt.Errorf("remove temp clone dir after clone failed: %w", err) @@ -112,7 +134,7 @@ func Clone( return &GoGitRepo{ config: config, - tree: worktree, + tree: &worktree{Worktree: tree}, opts: opts, decryptedPassword: string(decrypted), repo: repo, @@ -254,13 +276,13 @@ func (g *GoGitRepo) ReadTree(ctx context.Context, ref string) ([]repository.File treePath = safepath.Clean(treePath) entries := make([]repository.FileTreeEntry, 0, 100) - err := util.Walk(g.tree.Filesystem, treePath, func(path string, info fs.FileInfo, err error) error { + err := util.Walk(g.tree.Filesystem(), treePath, func(path string, info fs.FileInfo, err error) error { // We already have an error, just pass it onwards. if err != nil || // This is the root of the repository (or should pretend to be) safepath.Clean(path) == "" || path == treePath || // This is the Git data - (treePath == "" && strings.HasPrefix(path, ".git/")) { + (treePath == "" && (strings.HasPrefix(path, ".git/") || path == ".git")) { return err } if treePath != "" { @@ -280,9 +302,9 @@ func (g *GoGitRepo) ReadTree(ctx context.Context, ref string) ([]repository.File return err }) if errors.Is(err, fs.ErrNotExist) { - // We intentionally ignore this case, as + // We intentionally ignore this case, as it is expected } else if err != nil { - return nil, fmt.Errorf("failed to walk tree for ref '%s': %w", ref, err) + return nil, fmt.Errorf("walk tree for ref '%s': %w", ref, err) } return entries, nil } @@ -300,30 +322,33 @@ func (g *GoGitRepo) Update(ctx context.Context, path string, ref string, data [] // Create implements repository.Repository. func (g *GoGitRepo) Create(ctx context.Context, path string, ref string, data []byte, message string) error { + // FIXME: this means we would override files return g.Write(ctx, path, ref, data, message) } // Write implements repository.Repository. func (g *GoGitRepo) Write(ctx context.Context, fpath string, ref string, data []byte, message string) error { - fpath = safepath.Join(g.config.Spec.GitHub.Path, fpath) if err := verifyPathWithoutRef(fpath, ref); err != nil { return err } + fpath = safepath.Join(g.config.Spec.GitHub.Path, fpath) + // FIXME: this means that won't export empty folders + // should we create them with a .keep file? // For folders, just create the folder and ignore the commit if safepath.IsDir(fpath) { - return g.tree.Filesystem.MkdirAll(fpath, 0750) + return g.tree.Filesystem().MkdirAll(fpath, 0750) } dir := safepath.Dir(fpath) if dir != "" { - err := g.tree.Filesystem.MkdirAll(dir, 0750) + err := g.tree.Filesystem().MkdirAll(dir, 0750) if err != nil { return err } } - file, err := g.tree.Filesystem.Create(fpath) + file, err := g.tree.Filesystem().Create(fpath) if err != nil { return err } @@ -363,11 +388,16 @@ func (g *GoGitRepo) maybeCommit(ctx context.Context, message string) error { // Delete implements repository.Repository. func (g *GoGitRepo) Delete(ctx context.Context, fpath string, ref string, message string) error { - fpath = safepath.Join(g.config.Spec.GitHub.Path, fpath) if err := verifyPathWithoutRef(fpath, ref); err != nil { return err } + + fpath = safepath.Join(g.config.Spec.GitHub.Path, fpath) if _, err := g.tree.Remove(fpath); err != nil { + if errors.Is(err, fs.ErrNotExist) { + return repository.ErrFileNotFound + } + return err } return g.maybeCommit(ctx, message) @@ -379,11 +409,11 @@ func (g *GoGitRepo) Read(ctx context.Context, path string, ref string) (*reposit return nil, err } readPath := safepath.Join(g.config.Spec.GitHub.Path, path) - stat, err := g.tree.Filesystem.Lstat(readPath) + stat, err := g.tree.Filesystem().Lstat(readPath) if errors.Is(err, fs.ErrNotExist) { return nil, repository.ErrFileNotFound } else if err != nil { - return nil, fmt.Errorf("failed to stat path '%s': %w", readPath, err) + return nil, fmt.Errorf("stat path '%s': %w", readPath, err) } info := &repository.FileInfo{ Path: path, @@ -392,13 +422,13 @@ func (g *GoGitRepo) Read(ctx context.Context, path string, ref string) (*reposit }, } if !stat.IsDir() { - f, err := g.tree.Filesystem.Open(readPath) + f, err := g.tree.Filesystem().Open(readPath) if err != nil { - return nil, err + return nil, fmt.Errorf("open file '%s': %w", readPath, err) } info.Data, err = io.ReadAll(f) if err != nil { - return nil, err + return nil, fmt.Errorf("read file '%s': %w", readPath, err) } } return info, err diff --git a/pkg/registry/apis/provisioning/repository/go-git/wrapper_test.go b/pkg/registry/apis/provisioning/repository/go-git/wrapper_test.go index e4af86ac09b..8accb7f85cf 100644 --- a/pkg/registry/apis/provisioning/repository/go-git/wrapper_test.go +++ b/pkg/registry/apis/provisioning/repository/go-git/wrapper_test.go @@ -1,16 +1,28 @@ package gogit import ( + "bytes" "context" "encoding/json" + "errors" "fmt" + "io" + "io/fs" + "net/http" "os" "path/filepath" + "sort" "testing" "time" + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-git/v5" + plumbing "github.com/go-git/go-git/v5/plumbing" + githttp "github.com/go-git/go-git/v5/plumbing/transport/http" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + apierrors "k8s.io/apimachinery/pkg/api/errors" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" @@ -99,7 +111,7 @@ func TestReadTree(t *testing.T) { dir := t.TempDir() gitRepo, err := git.PlainInit(dir, false) require.NoError(t, err, "failed to init a new git repository") - worktree, err := gitRepo.Worktree() + tree, err := gitRepo.Worktree() require.NoError(t, err, "failed to get worktree") repo := &GoGitRepo{ @@ -123,8 +135,10 @@ func TestReadTree(t *testing.T) { decryptedPassword: "password", repo: gitRepo, - tree: worktree, - dir: dir, + tree: &worktree{ + Worktree: tree, + }, + dir: dir, } err = os.WriteFile(filepath.Join(dir, "test.txt"), []byte("test"), 0644) @@ -146,3 +160,1213 @@ func TestReadTree(t *testing.T) { require.Len(t, entries, 1, "entries from ReadTree") require.Equal(t, entries[0].Path, "test2.txt", "entry path") } + +func TestGoGitRepo_History(t *testing.T) { + repo := &GoGitRepo{ + config: &v0alpha1.Repository{ + ObjectMeta: v1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + Path: "grafana/", + }, + }, + }, + } + + // Test History method + ctx := context.Background() + _, err := repo.History(ctx, "test.txt", "") + require.Error(t, err, "History should return an error as it's not implemented") + require.Contains(t, err.Error(), "history is not yet implemented") +} + +func TestGoGitRepo_Validate(t *testing.T) { + repo := &GoGitRepo{ + config: &v0alpha1.Repository{ + ObjectMeta: v1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + Path: "grafana/", + }, + }, + }, + } + + // Test Validate method + errs := repo.Validate() + require.Empty(t, errs, "Validate should return no errors") +} + +func TestGoGitRepo_Webhook(t *testing.T) { + repo := &GoGitRepo{ + config: &v0alpha1.Repository{ + ObjectMeta: v1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + Path: "grafana/", + }, + }, + }, + } + + // Test Webhook method + ctx := context.Background() + _, err := repo.Webhook(ctx, nil) + require.Error(t, err, "Webhook should return an error as it's not implemented") + var statusErr *apierrors.StatusError + require.True(t, errors.As(err, &statusErr), "Error should be a StatusError") + require.Equal(t, http.StatusNotImplemented, int(statusErr.ErrStatus.Code)) + require.Contains(t, statusErr.ErrStatus.Message, "history is not yet implemented") +} + +func TestGoGitRepo_Read(t *testing.T) { + // Setup test cases + tests := []struct { + name string + path string + ref string + setupMock func(fs billy.Filesystem) + expectError bool + errorType error + checkResult func(t *testing.T, info *repository.FileInfo) + }{ + { + name: "successfully read file", + path: "test.txt", + ref: "", + setupMock: func(fs billy.Filesystem) { + // Create a test file + f, err := fs.Create("grafana/test.txt") + require.NoError(t, err, "failed to create test file") + _, err = f.Write([]byte("test content")) + require.NoError(t, err, "failed to write test content") + err = f.Close() + require.NoError(t, err, "failed to close test file") + }, + expectError: false, + checkResult: func(t *testing.T, info *repository.FileInfo) { + require.Equal(t, "test.txt", info.Path) + require.Equal(t, "test content", string(info.Data)) + require.NotNil(t, info.Modified) + }, + }, + { + name: "empty path", + path: "", + ref: "", + setupMock: func(fs billy.Filesystem) {}, + expectError: true, + errorType: fmt.Errorf("expected path"), + }, + { + name: "ref not supported", + path: "test.txt", + ref: "main", + setupMock: func(fs billy.Filesystem) {}, + expectError: true, + errorType: fmt.Errorf("ref unsupported"), + }, + { + name: "file not found", + path: "nonexistent.txt", + ref: "", + setupMock: func(fs billy.Filesystem) { + // Don't create the file + }, + expectError: true, + errorType: repository.ErrFileNotFound, + }, + { + name: "read directory", + path: "testdir", + ref: "", + setupMock: func(fs billy.Filesystem) { + // Create a test directory + err := fs.MkdirAll("grafana/testdir", 0755) + require.NoError(t, err, "failed to create test directory") + }, + expectError: false, + checkResult: func(t *testing.T, info *repository.FileInfo) { + require.Equal(t, "testdir", info.Path) + require.Nil(t, info.Data) + require.NotNil(t, info.Modified) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup filesystem and repo + fs := memfs.New() + tt.setupMock(fs) + + // Create a worktree with the filesystem + repo := &GoGitRepo{ + config: &v0alpha1.Repository{ + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + Path: "grafana/", + }, + }, + }, + tree: &worktree{ + Worktree: &git.Worktree{ + Filesystem: fs, + }, + }, + } + + // Test Read method + ctx := context.Background() + info, err := repo.Read(ctx, tt.path, tt.ref) + + // Check results + if tt.expectError { + require.Error(t, err) + if tt.errorType != nil { + if errors.Is(tt.errorType, repository.ErrFileNotFound) { + require.ErrorIs(t, err, repository.ErrFileNotFound) + } else { + require.Contains(t, err.Error(), tt.errorType.Error()) + } + } + } else { + require.NoError(t, err) + require.NotNil(t, info) + tt.checkResult(t, info) + } + }) + } +} + +func TestGoGitRepo_Delete(t *testing.T) { + tests := []struct { + name string + path string + ref string + pushOnWrite bool + setupMock func(mockTree *MockWorktree) + expectError bool + errorType error + }{ + { + name: "delete existing file", + path: "testfile.txt", + ref: "", + pushOnWrite: false, + setupMock: func(mockTree *MockWorktree) { + mockTree.On("Remove", "grafana/testfile.txt").Return(plumbing.Hash{}, nil) + }, + expectError: false, + }, + { + name: "delete non-existent file", + path: "nonexistent.txt", + ref: "", + pushOnWrite: false, + setupMock: func(mockTree *MockWorktree) { + mockTree.On("Remove", "grafana/nonexistent.txt").Return(plumbing.Hash{}, fs.ErrNotExist) + }, + expectError: true, + errorType: repository.ErrFileNotFound, + }, + { + name: "delete with other error", + path: "testfile.txt", + ref: "", + pushOnWrite: false, + setupMock: func(mockTree *MockWorktree) { + mockTree.On("Remove", "grafana/testfile.txt").Return(plumbing.Hash{}, fmt.Errorf("some other error")) + }, + expectError: true, + errorType: fmt.Errorf("some other error"), + }, + { + name: "empty path", + path: "", + ref: "", + pushOnWrite: false, + setupMock: func(mockTree *MockWorktree) {}, + expectError: true, + errorType: fmt.Errorf("expected path"), + }, + { + name: "with ref", + path: "testfile.txt", + ref: "main", + pushOnWrite: false, + setupMock: func(mockTree *MockWorktree) { + }, + expectError: true, + errorType: fmt.Errorf("ref unsupported"), + }, + { + name: "delete with push on write enabled", + path: "testfile.txt", + ref: "", + pushOnWrite: true, + setupMock: func(mockTree *MockWorktree) { + mockTree.On("Remove", "grafana/testfile.txt").Return(plumbing.Hash{}, nil) + mockTree.On("Commit", "test delete", mock.MatchedBy(func(opts *git.CommitOptions) bool { + return opts.Author != nil && + opts.Author.Name == "Test User" && + opts.Author.Email == "test@example.com" && + opts.Author.When.After(time.Now().Add(-time.Minute)) && + opts.Author.When.Before(time.Now().Add(time.Minute)) + })).Return(plumbing.Hash{}, nil) + }, + expectError: false, + }, + { + name: "delete with empty commit", + path: "testfile.txt", + ref: "", + pushOnWrite: true, + setupMock: func(mockTree *MockWorktree) { + mockTree.On("Remove", "grafana/testfile.txt").Return(plumbing.Hash{}, nil) + mockTree.On("Commit", "test delete", mock.MatchedBy(func(opts *git.CommitOptions) bool { + return opts.Author != nil && + opts.Author.Name == "Test User" && + opts.Author.Email == "test@example.com" && + opts.Author.When.After(time.Now().Add(-time.Minute)) && + opts.Author.When.Before(time.Now().Add(time.Minute)) + })).Return(plumbing.Hash{}, git.ErrEmptyCommit) + }, + expectError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup filesystem and repo + + mockTree := NewMockWorktree(t) + tt.setupMock(mockTree) + + // Create a worktree with the filesystem + repo := &GoGitRepo{ + config: &v0alpha1.Repository{ + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + Path: "grafana/", + }, + }, + }, + tree: mockTree, + opts: repository.CloneOptions{ + PushOnWrites: tt.pushOnWrite, + }, + } + + // Test Delete method + ctx := context.Background() + // Set author signature for the test + ctx = repository.WithAuthorSignature(ctx, repository.CommitSignature{ + Name: "Test User", + Email: "test@example.com", + When: time.Now(), + }) + + err := repo.Delete(ctx, tt.path, tt.ref, "test delete") + + // Check results + if tt.expectError { + require.Error(t, err) + if tt.errorType != nil { + if errors.Is(tt.errorType, repository.ErrFileNotFound) { + require.ErrorIs(t, err, repository.ErrFileNotFound) + } else { + require.Contains(t, err.Error(), tt.errorType.Error()) + } + } + } else { + require.NoError(t, err) + } + + mockTree.AssertExpectations(t) + }) + } +} + +// FIXME: missing coverage for Update / Create because we use Write for both +// when I think it shouldn't be the case as it's inconsistent with the other repository implementations +func TestGoGitRepo_Write(t *testing.T) { + tests := []struct { + name string + path string + ref string + data []byte + pushOnWrite bool + setupMock func(mockTree *MockWorktree) + expectError bool + errorType error + }{ + { + name: "successful write", + path: "test.txt", + ref: "", + data: []byte("test content"), + pushOnWrite: true, + setupMock: func(mockTree *MockWorktree) { + fs := memfs.New() + mockTree.On("Filesystem").Return(fs) + mockTree.On("Add", "grafana/test.txt").Return(plumbing.NewHash("abc123"), nil) + mockTree.On("Commit", "test write", mock.MatchedBy(func(opts *git.CommitOptions) bool { + return opts.Author != nil && + opts.Author.Name == "Test User" && + opts.Author.Email == "test@example.com" && + opts.Author.When.After(time.Now().Add(-time.Minute)) && + opts.Author.When.Before(time.Now().Add(time.Minute)) + })).Return(plumbing.NewHash("def456"), nil) + }, + expectError: false, + }, + { + name: "create folder only", + path: "testdir/", + ref: "", + data: []byte{}, + pushOnWrite: true, + setupMock: func(mockTree *MockWorktree) { + fs := memfs.New() + mockTree.On("Filesystem").Return(fs) + // No Add or Commit calls expected for directory creation + }, + expectError: false, + }, + { + name: "successful write without commit", + path: "test.txt", + ref: "", + data: []byte("test content"), + pushOnWrite: false, + setupMock: func(mockTree *MockWorktree) { + fs := memfs.New() + mockTree.On("Filesystem").Return(fs) + mockTree.On("Add", "grafana/test.txt").Return(plumbing.NewHash("abc123"), nil) + }, + expectError: false, + }, + { + name: "write with directory creation", + path: "dir/test.txt", + ref: "", + data: []byte("test content"), + pushOnWrite: true, + setupMock: func(mockTree *MockWorktree) { + fs := memfs.New() + mockTree.On("Filesystem").Return(fs) + mockTree.On("Add", "grafana/dir/test.txt").Return(plumbing.NewHash("abc123"), nil) + mockTree.On("Commit", "test write", mock.Anything).Return(plumbing.NewHash("def456"), nil) + }, + expectError: false, + }, + { + name: "error on add", + path: "test.txt", + ref: "", + data: []byte("test content"), + pushOnWrite: true, + setupMock: func(mockTree *MockWorktree) { + fs := memfs.New() + mockTree.On("Filesystem").Return(fs) + mockTree.On("Add", "grafana/test.txt").Return(plumbing.NewHash(""), fmt.Errorf("add error")) + }, + expectError: true, + errorType: fmt.Errorf("add error"), + }, + { + name: "error with ref", + path: "test.txt", + ref: "main", + data: []byte("test content"), + pushOnWrite: true, + setupMock: func(mockTree *MockWorktree) { + // No mock setup needed as it should fail before using the mock + }, + expectError: true, + errorType: fmt.Errorf("ref unsupported"), + }, + { + name: "empty path", + path: "", + ref: "", + data: []byte("test content"), + pushOnWrite: true, + setupMock: func(mockTree *MockWorktree) { + // No mock setup needed as it should fail before using the mock + }, + expectError: true, + errorType: fmt.Errorf("expected path"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup filesystem and repo + mockTree := NewMockWorktree(t) + tt.setupMock(mockTree) + + // Create a worktree with the filesystem + repo := &GoGitRepo{ + config: &v0alpha1.Repository{ + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + Path: "grafana/", + }, + }, + }, + tree: mockTree, + opts: repository.CloneOptions{ + PushOnWrites: tt.pushOnWrite, + }, + } + + // Test Write method + ctx := context.Background() + // Set author signature for the test + ctx = repository.WithAuthorSignature(ctx, repository.CommitSignature{ + Name: "Test User", + Email: "test@example.com", + When: time.Now(), + }) + + err := repo.Update(ctx, tt.path, tt.ref, tt.data, "test write") + + // Check results + if tt.expectError { + require.Error(t, err) + if tt.errorType != nil { + require.Contains(t, err.Error(), tt.errorType.Error()) + } + } else { + require.NoError(t, err) + } + + mockTree.AssertExpectations(t) + }) + } +} + +func TestGoGitRepo_Test(t *testing.T) { + tests := []struct { + name string + treeInitialized bool + expectedResult bool + }{ + { + name: "tree is initialized", + treeInitialized: true, + expectedResult: true, + }, + { + name: "tree is not initialized", + treeInitialized: false, + expectedResult: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup mock tree + mockTree := NewMockWorktree(t) + + // Create repo with or without initialized tree + repo := &GoGitRepo{ + config: &v0alpha1.Repository{ + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + Path: "grafana/", + }, + }, + }, + tree: nil, + } + + if tt.treeInitialized { + repo.tree = mockTree + } + + // Test the Test method + ctx := context.Background() + result, err := repo.Test(ctx) + + // Verify results + require.NoError(t, err) + require.NotNil(t, result) + require.Equal(t, tt.expectedResult, result.Success) + }) + } +} + +func TestGoGitRepo_Config(t *testing.T) { + // Create a test repository configuration + testConfig := &v0alpha1.Repository{ + ObjectMeta: v1.ObjectMeta{ + Name: "test-repo", + Namespace: "test-namespace", + }, + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + Path: "grafana/", + }, + }, + } + + // Create a repository instance with the test configuration + repo := &GoGitRepo{ + config: testConfig, + tree: NewMockWorktree(t), + } + + // Call the Config method + result := repo.Config() + + // Verify the result + require.NotNil(t, result) + require.Equal(t, testConfig, result) + require.Equal(t, "test-repo", result.Name) + require.Equal(t, "test-namespace", result.Namespace) + require.Equal(t, "grafana/", result.Spec.GitHub.Path) +} + +func TestGoGitRepo_Remove(t *testing.T) { + tests := []struct { + name string + setupMock func(t *testing.T) (*GoGitRepo, string) + expectError bool + expectedErrMsg string + }{ + { + name: "successful removal", + setupMock: func(t *testing.T) (*GoGitRepo, string) { + // Create a temporary directory that will be removed + tempDir, err := os.MkdirTemp("", "test-repo-*") + require.NoError(t, err) + + // Create a repository instance + repo := &GoGitRepo{ + dir: tempDir, + config: &v0alpha1.Repository{ + ObjectMeta: v1.ObjectMeta{ + Name: "test-repo", + Namespace: "test-namespace", + }, + }, + } + + return repo, tempDir + }, + expectError: false, + }, + { + name: "directory already removed", + setupMock: func(t *testing.T) (*GoGitRepo, string) { + // Create a temporary directory + tempDir, err := os.MkdirTemp("", "test-repo-*") + require.NoError(t, err) + + // Remove it immediately to simulate it being already gone + err = os.RemoveAll(tempDir) + require.NoError(t, err) + + // Create a repository instance pointing to the removed directory + repo := &GoGitRepo{ + dir: tempDir, + config: &v0alpha1.Repository{ + ObjectMeta: v1.ObjectMeta{ + Name: "test-repo", + Namespace: "test-namespace", + }, + }, + } + + return repo, tempDir + }, + expectError: false, // RemoveAll doesn't error if directory doesn't exist + }, + { + name: "invalid directory path", + setupMock: func(t *testing.T) (*GoGitRepo, string) { + // Create a repository instance with an invalid directory path + // that should cause an error when trying to remove + invalidPath := string([]byte{0}) + + repo := &GoGitRepo{ + dir: invalidPath, + config: &v0alpha1.Repository{ + ObjectMeta: v1.ObjectMeta{ + Name: "test-repo", + Namespace: "test-namespace", + }, + }, + } + + return repo, invalidPath + }, + expectError: true, + expectedErrMsg: "invalid argument", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup the test + repo, _ := tt.setupMock(t) + + // Test the Remove method + ctx := context.Background() + err := repo.Remove(ctx) + + // Verify results + if tt.expectError { + require.Error(t, err) + if tt.expectedErrMsg != "" { + require.Contains(t, err.Error(), tt.expectedErrMsg) + } + } else { + require.NoError(t, err) + // Verify the directory no longer exists + _, statErr := os.Stat(repo.dir) + require.True(t, os.IsNotExist(statErr), "Directory should not exist after removal") + } + }) + } +} + +func TestGoGitRepo_Push(t *testing.T) { + tests := []struct { + name string + setupMock func(t *testing.T) (*GoGitRepo, *MockRepository, *MockWorktree) + pushOpts repository.PushOptions + expectError bool + errorType error + }{ + { + name: "successful push", + setupMock: func(t *testing.T) (*GoGitRepo, *MockRepository, *MockWorktree) { + mockRepo := NewMockRepository(t) + mockRepo.On("PushContext", mock.Anything, mock.MatchedBy(func(o *git.PushOptions) bool { + if o.Auth == nil { + return false + } + // Verify we're using basic auth with expected credentials + basicAuth, ok := o.Auth.(*githttp.BasicAuth) + if !ok { + return false + } + return basicAuth.Username == "grafana" && basicAuth.Password == "test-token" + })).Return(nil) + + repo := &GoGitRepo{ + config: &v0alpha1.Repository{ + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + Path: "grafana/", + }, + }, + }, + repo: mockRepo, + decryptedPassword: "test-token", + opts: repository.CloneOptions{ + PushOnWrites: true, + }, + } + + return repo, mockRepo, nil + }, + pushOpts: repository.PushOptions{}, + expectError: false, + }, + { + name: "push error", + setupMock: func(t *testing.T) (*GoGitRepo, *MockRepository, *MockWorktree) { + mockRepo := NewMockRepository(t) + mockRepo.On("PushContext", mock.Anything, mock.Anything).Return(fmt.Errorf("network error")) + + repo := &GoGitRepo{ + config: &v0alpha1.Repository{ + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + Path: "grafana/", + }, + }, + }, + repo: mockRepo, + decryptedPassword: "test-token", + opts: repository.CloneOptions{ + PushOnWrites: true, + }, + } + + return repo, mockRepo, nil + }, + pushOpts: repository.PushOptions{}, + expectError: true, + errorType: fmt.Errorf("network error"), + }, + { + name: "already up to date", + setupMock: func(t *testing.T) (*GoGitRepo, *MockRepository, *MockWorktree) { + mockRepo := NewMockRepository(t) + mockRepo.On("PushContext", mock.Anything, mock.Anything).Return(git.NoErrAlreadyUpToDate) + + repo := &GoGitRepo{ + config: &v0alpha1.Repository{ + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + Path: "grafana/", + }, + }, + }, + repo: mockRepo, + decryptedPassword: "test-token", + opts: repository.CloneOptions{ + PushOnWrites: true, + }, + } + + return repo, mockRepo, nil + }, + pushOpts: repository.PushOptions{}, + expectError: false, + }, + { + name: "push with custom timeout", + setupMock: func(t *testing.T) (*GoGitRepo, *MockRepository, *MockWorktree) { + mockRepo := NewMockRepository(t) + mockRepo.On("PushContext", mock.Anything, mock.Anything).Return(nil) + + repo := &GoGitRepo{ + config: &v0alpha1.Repository{ + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + Path: "grafana/", + }, + }, + }, + repo: mockRepo, + decryptedPassword: "test-token", + opts: repository.CloneOptions{ + PushOnWrites: true, + }, + } + + return repo, mockRepo, nil + }, + pushOpts: repository.PushOptions{ + Timeout: 5 * time.Minute, + }, + expectError: false, + }, + { + name: "push with custom progress writer", + setupMock: func(t *testing.T) (*GoGitRepo, *MockRepository, *MockWorktree) { + mockRepo := NewMockRepository(t) + mockRepo.On("PushContext", mock.Anything, mock.MatchedBy(func(o *git.PushOptions) bool { + return o.Progress != nil && o.Progress != io.Discard + })).Return(nil) + + repo := &GoGitRepo{ + config: &v0alpha1.Repository{ + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + Path: "grafana/", + }, + }, + }, + repo: mockRepo, + decryptedPassword: "test-token", + opts: repository.CloneOptions{ + PushOnWrites: true, + }, + } + + return repo, mockRepo, nil + }, + pushOpts: repository.PushOptions{ + Progress: &bytes.Buffer{}, + }, + expectError: false, + }, + { + name: "push with BeforeFn success", + setupMock: func(t *testing.T) (*GoGitRepo, *MockRepository, *MockWorktree) { + mockRepo := NewMockRepository(t) + mockRepo.On("PushContext", mock.Anything, mock.Anything).Return(nil) + + repo := &GoGitRepo{ + config: &v0alpha1.Repository{ + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + Path: "grafana/", + }, + }, + }, + repo: mockRepo, + decryptedPassword: "test-token", + opts: repository.CloneOptions{ + PushOnWrites: true, + }, + } + + return repo, mockRepo, nil + }, + pushOpts: repository.PushOptions{ + BeforeFn: func() error { + return nil + }, + }, + expectError: false, + }, + { + name: "push with BeforeFn error", + setupMock: func(t *testing.T) (*GoGitRepo, *MockRepository, *MockWorktree) { + // No mock expectations since BeforeFn will fail before PushContext is called + + repo := &GoGitRepo{ + config: &v0alpha1.Repository{ + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + Path: "grafana/", + }, + }, + }, + repo: NewMockRepository(t), + decryptedPassword: "test-token", + opts: repository.CloneOptions{ + PushOnWrites: true, + }, + } + + return repo, repo.repo.(*MockRepository), nil + }, + pushOpts: repository.PushOptions{ + BeforeFn: func() error { + return fmt.Errorf("before function failed") + }, + }, + expectError: true, + errorType: fmt.Errorf("before function failed"), + }, + { + name: "push with PushOnWrites=false commits changes", + setupMock: func(t *testing.T) (*GoGitRepo, *MockRepository, *MockWorktree) { + mockRepo := NewMockRepository(t) + mockRepo.On("PushContext", mock.Anything, mock.Anything).Return(nil) + + mockTree := NewMockWorktree(t) + mockTree.On("Commit", "exported from grafana", mock.MatchedBy(func(o *git.CommitOptions) bool { + return o.All == true + })).Return(plumbing.NewHash("abc123"), nil) + + repo := &GoGitRepo{ + config: &v0alpha1.Repository{ + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + Path: "grafana/", + }, + }, + }, + repo: mockRepo, + tree: mockTree, + decryptedPassword: "test-token", + opts: repository.CloneOptions{ + PushOnWrites: false, + }, + } + + return repo, mockRepo, mockTree + }, + pushOpts: repository.PushOptions{}, + expectError: false, + }, + { + name: "push with PushOnWrites=false and empty commit", + setupMock: func(t *testing.T) (*GoGitRepo, *MockRepository, *MockWorktree) { + mockRepo := NewMockRepository(t) + mockRepo.On("PushContext", mock.Anything, mock.Anything).Return(nil) + + mockTree := NewMockWorktree(t) + mockTree.On("Commit", "exported from grafana", mock.Anything).Return(plumbing.ZeroHash, git.ErrEmptyCommit) + + repo := &GoGitRepo{ + config: &v0alpha1.Repository{ + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + Path: "grafana/", + }, + }, + }, + repo: mockRepo, + tree: mockTree, + decryptedPassword: "test-token", + opts: repository.CloneOptions{ + PushOnWrites: false, + }, + } + + return repo, mockRepo, mockTree + }, + pushOpts: repository.PushOptions{}, + expectError: false, + }, + { + name: "push with PushOnWrites=false and commit error", + setupMock: func(t *testing.T) (*GoGitRepo, *MockRepository, *MockWorktree) { + mockTree := NewMockWorktree(t) + mockTree.On("Commit", "exported from grafana", mock.Anything).Return(plumbing.ZeroHash, fmt.Errorf("commit error")) + + repo := &GoGitRepo{ + config: &v0alpha1.Repository{ + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + Path: "grafana/", + }, + }, + }, + repo: NewMockRepository(t), + tree: mockTree, + decryptedPassword: "test-token", + opts: repository.CloneOptions{ + PushOnWrites: false, + }, + } + + return repo, repo.repo.(*MockRepository), mockTree + }, + pushOpts: repository.PushOptions{}, + expectError: true, + errorType: fmt.Errorf("commit error"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup the test + repo, mockRepo, mockTree := tt.setupMock(t) + + // Test the Push method + ctx := context.Background() + err := repo.Push(ctx, tt.pushOpts) + + // Verify results + if tt.expectError { + require.Error(t, err) + if tt.errorType != nil { + require.Contains(t, err.Error(), tt.errorType.Error()) + } + } else { + require.NoError(t, err) + } + + // Verify mock expectations if mocks were created + if mockRepo != nil { + mockRepo.AssertExpectations(t) + } + if mockTree != nil { + mockTree.AssertExpectations(t) + } + }) + } +} +func TestGoGitRepo_ReadTree(t *testing.T) { + tests := []struct { + name string + setupMock func(t *testing.T) *GoGitRepo + ref string + expectError bool + expectedErrMsg string + expectedFiles []repository.FileTreeEntry + }{ + { + name: "successful read with files", + setupMock: func(t *testing.T) *GoGitRepo { + mockFS := memfs.New() + + // Create test files in the mock filesystem + require.NoError(t, mockFS.MkdirAll("grafana/folder1", 0750)) + file1, err := mockFS.Create("grafana/file1.txt") + require.NoError(t, err) + _, err = file1.Write([]byte("test content")) + require.NoError(t, err) + require.NoError(t, file1.Close()) + + file2, err := mockFS.Create("grafana/folder1/file2.txt") + require.NoError(t, err) + _, err = file2.Write([]byte("nested file content")) + require.NoError(t, err) + require.NoError(t, file2.Close()) + + mockTree := NewMockWorktree(t) + mockTree.On("Filesystem").Return(mockFS) + + return &GoGitRepo{ + config: &v0alpha1.Repository{ + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + Path: "grafana/", + }, + }, + }, + tree: mockTree, + } + }, + ref: "main", + expectError: false, + expectedFiles: []repository.FileTreeEntry{ + {Path: "file1.txt", Size: 12, Blob: true, Hash: "TODO/12"}, + {Path: "folder1", Size: 0, Blob: false}, + {Path: "folder1/file2.txt", Size: 19, Blob: true, Hash: "TODO/19"}, + }, + }, + { + name: "filesystem error", + setupMock: func(t *testing.T) *GoGitRepo { + mockTree := NewMockWorktree(t) + mockFS := memfs.New() + + // Create a filesystem that will return an error when accessed + mockTree.On("Filesystem").Return(mockFS) + + return &GoGitRepo{ + config: &v0alpha1.Repository{ + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + Path: "non-existent-path/", + }, + }, + }, + tree: mockTree, + } + }, + ref: "main", + expectError: false, // ReadTree handles fs.ErrNotExist by returning empty entries + expectedFiles: []repository.FileTreeEntry{}, + }, + { + name: "successful read with empty path", + setupMock: func(t *testing.T) *GoGitRepo { + mockFS := memfs.New() + + // Create test files in the mock filesystem + file1, err := mockFS.Create("file1.txt") + require.NoError(t, err) + _, err = file1.Write([]byte("test content")) + require.NoError(t, err) + require.NoError(t, file1.Close()) + + require.NoError(t, mockFS.MkdirAll("folder1", 0750)) + file2, err := mockFS.Create("folder1/file2.txt") + require.NoError(t, err) + _, err = file2.Write([]byte("nested file content")) + require.NoError(t, err) + require.NoError(t, file2.Close()) + + // Create .git directory which should be ignored + require.NoError(t, mockFS.MkdirAll(".git", 0750)) + gitFile, err := mockFS.Create(".git/config") + require.NoError(t, err) + _, err = gitFile.Write([]byte("git config")) + require.NoError(t, err) + require.NoError(t, gitFile.Close()) + + mockTree := NewMockWorktree(t) + mockTree.On("Filesystem").Return(mockFS) + + return &GoGitRepo{ + config: &v0alpha1.Repository{ + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + Path: "", + }, + }, + }, + tree: mockTree, + } + }, + ref: "main", + expectError: false, + expectedFiles: []repository.FileTreeEntry{ + {Path: "file1.txt", Size: 12, Blob: true, Hash: "TODO/12"}, + {Path: "folder1", Size: 0, Blob: false}, + {Path: "folder1/file2.txt", Size: 19, Blob: true, Hash: "TODO/19"}, + }, + }, + { + name: "filesystem error", + setupMock: func(t *testing.T) *GoGitRepo { + mockTree := NewMockWorktree(t) + mockFS := memfs.New() + + // Create a filesystem that will return an error when accessed + mockTree.On("Filesystem").Return(mockFS) + + return &GoGitRepo{ + config: &v0alpha1.Repository{ + Spec: v0alpha1.RepositorySpec{ + GitHub: &v0alpha1.GitHubRepositoryConfig{ + Path: "non-existent-path/", + }, + }, + }, + tree: mockTree, + } + }, + ref: "main", + expectError: false, // ReadTree handles fs.ErrNotExist by returning empty entries + expectedFiles: []repository.FileTreeEntry{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup the test + repo := tt.setupMock(t) + + // Test the ReadTree method + ctx := context.Background() + entries, err := repo.ReadTree(ctx, tt.ref) + + // Verify results + if tt.expectError { + require.Error(t, err) + if tt.expectedErrMsg != "" { + require.Contains(t, err.Error(), tt.expectedErrMsg) + } + } else { + require.NoError(t, err) + + // Sort entries for consistent comparison + sort.Slice(entries, func(i, j int) bool { + return entries[i].Path < entries[j].Path + }) + sort.Slice(tt.expectedFiles, func(i, j int) bool { + return tt.expectedFiles[i].Path < tt.expectedFiles[j].Path + }) + + require.Equal(t, len(tt.expectedFiles), len(entries), "Number of entries should match") + for i, expected := range tt.expectedFiles { + require.Equal(t, expected.Path, entries[i].Path, "Path should match") + require.Equal(t, expected.Size, entries[i].Size, "Size should match") + require.Equal(t, expected.Blob, entries[i].Blob, "Blob flag should match") + if expected.Blob { + require.Equal(t, expected.Hash, entries[i].Hash, "Hash should match") + } + } + } + + // Verify mock expectations + repo.tree.(*MockWorktree).AssertExpectations(t) + }) + } +} From cf1b964829a20d0686d0e571002ab7a056a19dd0 Mon Sep 17 00:00:00 2001 From: Pepe Cano <825430+ppcano@users.noreply.github.com> Date: Wed, 23 Apr 2025 15:15:12 +0200 Subject: [PATCH 053/146] docs(alerting): clarify transition to Recovering state when using recovery thresholds (#104381) --- .../alerting/fundamentals/alert-rules/queries-conditions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sources/alerting/fundamentals/alert-rules/queries-conditions.md b/docs/sources/alerting/fundamentals/alert-rules/queries-conditions.md index d7c2f939ff4..dde43d6a6aa 100644 --- a/docs/sources/alerting/fundamentals/alert-rules/queries-conditions.md +++ b/docs/sources/alerting/fundamentals/alert-rules/queries-conditions.md @@ -137,7 +137,7 @@ If the threshold is set as the alert condition, the alert fires when the thresho ### Recovery threshold -To reduce the noise from flapping alerts, you can set a recovery threshold so that the alert returns to the `Normal` state only after the recovery threshold is crossed. +To reduce the noise from flapping alerts, you can set a recovery threshold so that the alert returns to the `Normal` or `Recovering` state only after the recovery threshold is crossed. Flapping alerts occur when the query value repeatedly crosses above and below the alert threshold, causing frequent state changes. This results in a series of firing-resolved-firing notifications and a noisy alert state history. @@ -149,8 +149,8 @@ For example, if you have an alert for latency with a threshold of 1000ms and the To prevent this, you can set a recovery threshold to define two thresholds instead of one: -1. An alert transitions to the `Pending` or `Alerting` state when the alert threshold is crossed. -1. An alert transitions back to `Normal` state only after the recovery threshold is crossed. +1. An alert transitions to the `Pending` or `Alerting` state when it crosses the alert threshold. +1. It then transitions to the `Recovering` or `Normal` state only when it crosses the recovery threshold. In the previous example, setting the recovery threshold to 900ms means the alert only resolves when the latency falls below 900ms: From b7c0e8bd330ccc56a0c88b570e062dfb37186344 Mon Sep 17 00:00:00 2001 From: Scott Lepper Date: Wed, 23 Apr 2025 09:17:47 -0400 Subject: [PATCH 054/146] Scenes: add support for sourcemaps when linking (#104328) Scenes: add support for sourcemaps when linking --- scripts/webpack/webpack.dev.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/scripts/webpack/webpack.dev.js b/scripts/webpack/webpack.dev.js index 7d322fbf65b..20c8c38c46c 100644 --- a/scripts/webpack/webpack.dev.js +++ b/scripts/webpack/webpack.dev.js @@ -4,6 +4,7 @@ const browserslist = require('browserslist'); const { resolveToEsbuildTarget } = require('esbuild-plugin-browserslist'); const ESLintPlugin = require('eslint-webpack-plugin'); const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); +const fs = require('fs'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const path = require('path'); const { DefinePlugin, EnvironmentPlugin } = require('webpack'); @@ -29,6 +30,21 @@ function getDecoupledPlugins() { return packages.filter((pkg) => pkg.dir.includes('plugins/datasource')).map((pkg) => `${pkg.dir}/**`); } +// When linking scenes for development, resolve the path to the src directory for sourcemaps +function scenesModule() { + const scenesPath = path.resolve('./node_modules/@grafana/scenes'); + try { + const status = fs.lstatSync(scenesPath); + if (status.isSymbolicLink()) { + console.log(`scenes is linked to local scenes repo`); + return path.resolve(scenesPath + '/src'); + } + } catch (error) { + console.error(`Error checking scenes path: ${error.message}`); + } + return scenesPath; +} + const envConfig = getEnvConfig(); module.exports = (env = {}) => { @@ -55,6 +71,7 @@ module.exports = (env = {}) => { // This is required to correctly resolve react-router-dom when linking with // local version of @grafana/scenes 'react-router-dom': path.resolve('./node_modules/react-router-dom'), + '@grafana/scenes': scenesModule(), }, }, From 5c44ad2763bf5f4405986d127faf3901a7ca7132 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Wed, 23 Apr 2025 14:30:35 +0100 Subject: [PATCH 055/146] Chore: Initial scaffolding for crowdin action (#104393) * start to scaffold github action * test script * simplify * right path (maybe) * clean up * CONSTANT_CASE * add CODEOWNERS * kick CI --- .github/CODEOWNERS | 2 + .../workflows/i18n-crowdin-create-tasks.yml | 25 ++++++ .../workflows/scripts/crowdin/create-tasks.js | 84 +++++++++++++++++++ package.json | 1 + yarn.lock | 12 ++- 5 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/i18n-crowdin-create-tasks.yml create mode 100644 .github/workflows/scripts/crowdin/create-tasks.js diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 59e6164cb87..67ceca61366 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -814,6 +814,8 @@ embed.go @grafana/grafana-as-code /.github/workflows/core-plugins-build-and-release.yml @grafana/plugins-platform-frontend @grafana/plugins-platform-backend /.github/workflows/i18n-crowdin-upload.yml @grafana/grafana-frontend-platform /.github/workflows/i18n-crowdin-download.yml @grafana/grafana-frontend-platform +/.github/workflows/i18n-crowdin-create-tasks.yml @grafana/grafana-frontend-platform +/.github/workflows/scripts/crowdin/create-tasks.js @grafana/grafana-frontend-platform /.github/workflows/pr-go-workspace-check.yml @grafana/grafana-app-platform-squad /.github/workflows/pr-dependabot-update-go-workspace.yml @grafana/grafana-app-platform-squad /.github/workflows/pr-k8s-codegen-check.yml @grafana/grafana-app-platform-squad diff --git a/.github/workflows/i18n-crowdin-create-tasks.yml b/.github/workflows/i18n-crowdin-create-tasks.yml new file mode 100644 index 00000000000..f12ca3ff7a2 --- /dev/null +++ b/.github/workflows/i18n-crowdin-create-tasks.yml @@ -0,0 +1,25 @@ +name: Crowdin Create Tasks + +on: + workflow_dispatch: + # schedule: + # - cron: "0 0 * * *" + +jobs: + create-tasks-in-crowdin: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + + - name: Create tasks + env: + CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} + CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} + run: node ./.github/workflows/scripts/crowdin/create-tasks.js diff --git a/.github/workflows/scripts/crowdin/create-tasks.js b/.github/workflows/scripts/crowdin/create-tasks.js new file mode 100644 index 00000000000..d3085f8afa0 --- /dev/null +++ b/.github/workflows/scripts/crowdin/create-tasks.js @@ -0,0 +1,84 @@ +const crowdin = require('@crowdin/crowdin-api-client'); +const TRANSLATED_CONNECTOR_DESCRIPTION = '{{tos_service_type: premium}}'; + +const API_TOKEN = process.env.CROWDIN_PERSONAL_TOKEN; +if (!API_TOKEN) { + console.error('Error: CROWDIN_PERSONAL_TOKEN environment variable is not set'); + process.exit(1); +} + +const PROJECT_ID = process.env.CROWDIN_PROJECT_ID; +if (!PROJECT_ID) { + console.error('Error: CROWDIN_PROJECT_ID environment variable is not set'); + process.exit(1); +} + +const { tasksApi, projectsGroupsApi, sourceFilesApi } = new crowdin.default({ + token: API_TOKEN, + organization: 'grafana' +}); + +const languages = await getLanguages(); +const fileIds = await getFileIds(); +console.log('Languages: ', languages); +console.log('File IDs: ', fileIds); + +// for (const language of languages) { +// const { name, id } = language; +// await createTask(`Translate to ${name}`, id, fileIds); +// } + +async function getLanguages() { + try { + const project = await projectsGroupsApi.getProject(PROJECT_ID); + const languages = project.data.targetLanguages; + return languages; + } catch (error) { + console.error('Failed to fetch languages: ', error.message); + if (error.response && error.response.data) { + console.error('Error details: ', JSON.stringify(error.response.data, null, 2)); + } + process.exit(1); + } +} + +async function getFileIds() { + try { + const response = await sourceFilesApi.listProjectFiles(PROJECT_ID); + const files = response.data; + const fileIds = files.map(file => file.data.id); + return fileIds; + } catch (error) { + console.error('Failed to fetch file IDs: ', error.message); + if (error.response && error.response.data) { + console.error('Error details: ', JSON.stringify(error.response.data, null, 2)); + } + process.exit(1); + } +} + +async function createTask(title, languageId, fileIds) { + try { + const taskParams = { + title, + description: TRANSLATED_CONNECTOR_DESCRIPTION, + languageId, + type: 2, // Translation by vendor + workflowStepId: 78, // Translation step ID + skipAssignedStrings: true, + fileIds, + }; + + console.log(`Creating Crowdin task: "${title}" for language ${languageId}`); + + const response = await tasksApi.addTask(PROJECT_ID, taskParams); + console.log(`Task created successfully! Task ID: ${response.data.id}`); + return response.data; + } catch (error) { + console.error('Failed to create Crowdin task: ', error.message); + if (error.response && error.response.data) { + console.error('Error details: ', JSON.stringify(error.response.data, null, 2)); + } + process.exit(1); + } +} diff --git a/package.json b/package.json index 9f91adb3250..5e9a44e8f99 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "@babel/runtime": "7.27.0", "@betterer/betterer": "5.4.0", "@betterer/cli": "5.4.0", + "@crowdin/crowdin-api-client": "^1.42.0", "@cypress/webpack-preprocessor": "6.0.4", "@emotion/eslint-plugin": "11.12.0", "@grafana/eslint-config": "8.0.0", diff --git a/yarn.lock b/yarn.lock index 263a8945131..70d29a1635c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1629,6 +1629,15 @@ __metadata: languageName: node linkType: hard +"@crowdin/crowdin-api-client@npm:^1.42.0": + version: 1.42.0 + resolution: "@crowdin/crowdin-api-client@npm:1.42.0" + dependencies: + axios: "npm:^1" + checksum: 10/9e816c5d1fe6f93c0f20625f377f0caa1c2d0cc2a5df97440fc3494ab1d3a7a29d6fe40af8fae32ea96f2bc7fa2bcdc1c7af6dc6563d8fdf86624dc7885e98ff + languageName: node + linkType: hard + "@cspotcode/source-map-support@npm:^0.8.0": version: 0.8.1 resolution: "@cspotcode/source-map-support@npm:0.8.1" @@ -11644,7 +11653,7 @@ __metadata: languageName: node linkType: hard -"axios@npm:^1.7.9, axios@npm:^1.8.2, axios@npm:^1.8.3": +"axios@npm:^1, axios@npm:^1.7.9, axios@npm:^1.8.2, axios@npm:^1.8.3": version: 1.8.4 resolution: "axios@npm:1.8.4" dependencies: @@ -17592,6 +17601,7 @@ __metadata: "@betterer/betterer": "npm:5.4.0" "@betterer/cli": "npm:5.4.0" "@bsull/augurs": "npm:^0.9.0" + "@crowdin/crowdin-api-client": "npm:^1.42.0" "@cypress/webpack-preprocessor": "npm:6.0.4" "@emotion/css": "npm:11.13.5" "@emotion/eslint-plugin": "npm:11.12.0" From 592c7a0b3cc3a16ee9d7f894e1952de76033e814 Mon Sep 17 00:00:00 2001 From: Matias Chomicki Date: Wed, 23 Apr 2025 16:01:49 +0200 Subject: [PATCH 056/146] Log Controls: Allow plugins to use Log Controls (#104237) * Logs Panel: add showControls option * Make showControls optional * Logs Panel: expose storage option * Controlled log rows: allow table props to be possibly undefined * Logs panel: expose controlled options change callback * Logs panel: pass new callback * Logs Panel: pass the correct field callbacks * LogListControls: disallow unique labels in apps * LogListControls: allow to filter by unknown level * LogListControls: fix wrong aria-pressed state * LogListControls: hide overflow * ControlledLogRows: make scroll auto * Controlled Logs Panel: forward scroll ref * chore: generalize isCoreApp * Formatting * LogListControls: update test * Logs Panel: make sure tests pass with and without controls * formatting * Losg Panel: add comments for the new options * Log list controls: Add comment --- .../logs/panelcfg/x/LogsPanelCfg_types.gen.ts | 3 + .../logs/components/ControlledLogRows.tsx | 239 +++++---- .../logs/components/ControlledLogsTable.tsx | 5 + .../components/panel/LogListControls.test.tsx | 28 +- .../logs/components/panel/LogListControls.tsx | 6 +- public/app/plugins/panel/logs-new/types.ts | 12 +- .../app/plugins/panel/logs/LogsPanel.test.tsx | 489 ++++++++++-------- public/app/plugins/panel/logs/LogsPanel.tsx | 172 ++++-- public/app/plugins/panel/logs/panelcfg.cue | 3 + public/app/plugins/panel/logs/panelcfg.gen.ts | 3 + public/app/plugins/panel/logs/types.test.ts | 19 + public/app/plugins/panel/logs/types.ts | 13 +- 12 files changed, 611 insertions(+), 381 deletions(-) create mode 100644 public/app/plugins/panel/logs/types.test.ts diff --git a/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts index 39ef7f65f58..a73bc96ce5d 100644 --- a/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts @@ -13,6 +13,7 @@ import * as common from '@grafana/schema'; export const pluginVersion = "12.0.0-pre"; export interface Options { + controlsStorageKey?: string; dedupStrategy: common.LogsDedupStrategy; displayedFields?: Array; enableInfiniteScrolling?: boolean; @@ -29,9 +30,11 @@ export interface Options { onClickFilterString?: unknown; onClickHideField?: unknown; onClickShowField?: unknown; + onLogOptionsChange?: unknown; onNewLogsReceived?: unknown; prettifyLogMessage: boolean; showCommonLabels: boolean; + showControls?: boolean; showLabels: boolean; showLogContextToggle: boolean; showTime: boolean; diff --git a/public/app/features/logs/components/ControlledLogRows.tsx b/public/app/features/logs/components/ControlledLogRows.tsx index a08a7955f95..98a604233af 100644 --- a/public/app/features/logs/components/ControlledLogRows.tsx +++ b/public/app/features/logs/components/ControlledLogRows.tsx @@ -1,5 +1,5 @@ import { css } from '@emotion/css'; -import { useEffect, useMemo, useRef } from 'react'; +import { useEffect, useMemo, useRef, forwardRef, useImperativeHandle } from 'react'; import { AbsoluteTimeRange, @@ -35,12 +35,12 @@ export interface ControlledLogRowsProps extends Omit { /** Props added for Table **/ visualisationType: LogsVisualisationType; - splitOpen: SplitOpen; - panelState: ExploreLogsPanelState | undefined; - updatePanelState: (panelState: Partial) => void; + splitOpen?: SplitOpen; + panelState?: ExploreLogsPanelState; + updatePanelState?: (panelState: Partial) => void; datasourceType?: string; - width: number; - logsTableFrames: DataFrame[] | undefined; + width?: number; + logsTableFrames?: DataFrame[]; } export type LogRowsComponentProps = Omit< @@ -48,108 +48,129 @@ export type LogRowsComponentProps = Omit< 'app' | 'dedupStrategy' | 'showLabels' | 'showTime' | 'logsSortOrder' | 'prettifyLogMessage' | 'wrapLogMessage' >; -export const ControlledLogRows = ({ - deduplicatedRows, - dedupStrategy, - hasUnescapedContent, - showLabels, - showTime, - logsMeta, - logOptionsStorageKey, - logsSortOrder, - prettifyLogMessage, - onLogOptionsChange, - wrapLogMessage, - ...rest -}: ControlledLogRowsProps) => { - return ( - - {rest.visualisationType === 'logs' && } - {rest.visualisationType === 'table' && } - - ); -}; - -const LogRowsComponent = ({ loading, loadMoreLogs, deduplicatedRows = [], range, ...rest }: LogRowsComponentProps) => { - const { - app, - dedupStrategy, - filterLevels, - forceEscape, - prettifyJSON, - sortOrder, - showTime, - showUniqueLabels, - wrapLogMessage, - } = useLogListContext(); - const eventBus = useMemo(() => new EventBusSrv(), []); - const scrollElementRef = useRef(null); - - useEffect(() => { - const subscription = eventBus.subscribe(ScrollToLogsEvent, (e: ScrollToLogsEvent) => - handleScrollToEvent(e, scrollElementRef.current) - ); - return () => subscription.unsubscribe(); - }, [eventBus]); - - const filteredLogs = useMemo( - () => - filterLevels.length === 0 - ? deduplicatedRows - : deduplicatedRows.filter((log) => filterLevels.includes(log.logLevel)), - [filterLevels, deduplicatedRows] - ); - - return ( -
- -
( + ( + { + deduplicatedRows, + dedupStrategy, + hasUnescapedContent, + showLabels, + showTime, + logsMeta, + logOptionsStorageKey, + logsSortOrder, + prettifyLogMessage, + onLogOptionsChange, + wrapLogMessage, + ...rest + }: ControlledLogRowsProps, + ref + ) => { + return ( + - - + )} + {rest.visualisationType === 'table' && rest.panelState && rest.updatePanelState && ( + + )} + + ); + } +); + +ControlledLogRows.displayName = 'ControlledLogRows'; + +const LogRowsComponent = forwardRef( + ({ loading, loadMoreLogs, deduplicatedRows = [], range, ...rest }: LogRowsComponentProps, ref) => { + const { + app, + dedupStrategy, + filterLevels, + forceEscape, + prettifyJSON, + sortOrder, + showTime, + showUniqueLabels, + wrapLogMessage, + } = useLogListContext(); + const eventBus = useMemo(() => new EventBusSrv(), []); + const scrollElementRef = useRef(null); + + useEffect(() => { + const subscription = eventBus.subscribe(ScrollToLogsEvent, (e: ScrollToLogsEvent) => + handleScrollToEvent(e, scrollElementRef.current) + ); + return () => subscription.unsubscribe(); + }, [eventBus]); + + useImperativeHandle(ref, () => scrollElementRef.current); + + const filteredLogs = useMemo( + () => + filterLevels.length === 0 + ? deduplicatedRows + : deduplicatedRows.filter((log) => filterLevels.includes(log.logLevel)), + [filterLevels, deduplicatedRows] + ); + + const scrollElementClassName = useMemo(() => { + if (ref) { + return styles.forwardedScrollableLogRows; + } + return config.featureToggles.logsInfiniteScrolling ? styles.scrollableLogRows : styles.logRows; + }, [ref]); + + return ( +
+ +
+ - + sortOrder={sortOrder} + > + + +
-
- ); -}; + ); + } +); + +LogRowsComponent.displayName = 'LogRowsComponent'; function handleScrollToEvent(event: ScrollToLogsEvent, scrollElement: HTMLDivElement | null) { if (event.payload.scrollTo === 'top') { @@ -161,10 +182,15 @@ function handleScrollToEvent(event: ScrollToLogsEvent, scrollElement: HTMLDivEle const styles = { scrollableLogRows: css({ - overflowY: 'scroll', + overflowY: 'auto', width: '100%', maxHeight: '75vh', }), + forwardedScrollableLogRows: css({ + overflowY: 'auto', + width: '100%', + maxHeight: '100%', + }), logRows: css({ overflowX: 'scroll', overflowY: 'visible', @@ -173,5 +199,6 @@ const styles = { logRowsContainer: css({ display: 'flex', flexDirection: 'row-reverse', + height: '100%', }), }; diff --git a/public/app/features/logs/components/ControlledLogsTable.tsx b/public/app/features/logs/components/ControlledLogsTable.tsx index 5dfb5b5f6a7..2b41364aa82 100644 --- a/public/app/features/logs/components/ControlledLogsTable.tsx +++ b/public/app/features/logs/components/ControlledLogsTable.tsx @@ -32,6 +32,11 @@ export const ControlledLogsTable = ({ const theme = useTheme2(); const styles = getStyles(theme); + if (!splitOpen || !width || !updatePanelState) { + console.error(': Missing required props.'); + return; + } + return (
diff --git a/public/app/features/logs/components/panel/LogListControls.test.tsx b/public/app/features/logs/components/panel/LogListControls.test.tsx index 2471506d8d5..2950f635f4c 100644 --- a/public/app/features/logs/components/panel/LogListControls.test.tsx +++ b/public/app/features/logs/components/panel/LogListControls.test.tsx @@ -49,7 +49,7 @@ describe('LogListControls', () => { test('Renders legacy controls', () => { render( - + ); @@ -76,6 +76,28 @@ describe('LogListControls', () => { } ); + test('Renders a subset of options for plugins', () => { + render( + + + + ); + expect(screen.getByLabelText('Scroll to bottom')).toBeInTheDocument(); + expect(screen.getByLabelText('Oldest logs first')).toBeInTheDocument(); + expect(screen.getByLabelText('Deduplication')).toBeInTheDocument(); + expect(screen.getByLabelText('Display levels')).toBeInTheDocument(); + expect(screen.getByLabelText('Show timestamps')).toBeInTheDocument(); + expect(screen.getByLabelText('Wrap lines')).toBeInTheDocument(); + expect(screen.getByLabelText('Enable highlighting')).toBeInTheDocument(); + expect(screen.getByLabelText('Scroll to top')).toBeInTheDocument(); + expect(screen.queryByLabelText('Show unique labels')).not.toBeInTheDocument(); + expect(screen.queryByLabelText('Expand JSON logs')).not.toBeInTheDocument(); + expect( + screen.queryByLabelText('Fix incorrectly escaped newline and tab sequences in log lines') + ).not.toBeInTheDocument(); + expect(screen.queryByLabelText('Remove escaping')).not.toBeInTheDocument(); + }); + test('Allows to scroll', async () => { const eventBus = new EventBusSrv(); jest.spyOn(eventBus, 'publish'); @@ -185,13 +207,13 @@ describe('LogListControls', () => { test('Controls unique labels', async () => { const { rerender } = render( - + ); await userEvent.click(screen.getByLabelText('Show unique labels')); rerender( - + ); diff --git a/public/app/features/logs/components/panel/LogListControls.tsx b/public/app/features/logs/components/panel/LogListControls.tsx index 23159176475..6bfe64e9bfe 100644 --- a/public/app/features/logs/components/panel/LogListControls.tsx +++ b/public/app/features/logs/components/panel/LogListControls.tsx @@ -33,6 +33,7 @@ const FILTER_LEVELS: LogLevel[] = [ LogLevel.warning, LogLevel.error, LogLevel.critical, + LogLevel.unknown, ]; export const LogListControls = ({ eventBus, visualisationType = 'logs' }: Props) => { @@ -264,7 +265,8 @@ export const LogListControls = ({ eventBus, visualisationType = 'logs' }: Props) } size="lg" /> - {showUniqueLabels !== undefined && ( + {/* When this is used in a Plugin context, app is unknown */} + {showUniqueLabels !== undefined && app !== CoreApp.Unknown && ( @@ -389,6 +390,7 @@ const getStyles = (theme: GrafanaTheme2) => { paddingTop: theme.spacing(0.75), paddingLeft: theme.spacing(1), borderLeft: `solid 1px ${theme.colors.border.medium}`, + overflow: 'hidden', }), scrollToTopButton: css({ margin: 0, diff --git a/public/app/plugins/panel/logs-new/types.ts b/public/app/plugins/panel/logs-new/types.ts index 582cc94a1b8..c772acaef60 100644 --- a/public/app/plugins/panel/logs-new/types.ts +++ b/public/app/plugins/panel/logs-new/types.ts @@ -19,14 +19,6 @@ export function isLogsGrammar(grammar: unknown): grammar is Grammar { } export function isCoreApp(app: unknown): app is CoreApp { - return ( - app === CoreApp.CloudAlerting || - app === CoreApp.Correlations || - app === CoreApp.Dashboard || - app === CoreApp.Explore || - app === CoreApp.PanelEditor || - app === CoreApp.PanelViewer || - app === CoreApp.UnifiedAlerting || - app === CoreApp.Unknown - ); + const apps = Object.values(CoreApp).map((coreApp) => coreApp.toString()); + return typeof app === 'string' && apps.includes(app); } diff --git a/public/app/plugins/panel/logs/LogsPanel.test.tsx b/public/app/plugins/panel/logs/LogsPanel.test.tsx index 7def6ca6cb5..2127c0b7c51 100644 --- a/public/app/plugins/panel/logs/LogsPanel.test.tsx +++ b/public/app/plugins/panel/logs/LogsPanel.test.tsx @@ -145,10 +145,10 @@ beforeAll(() => { }); }); -describe('LogsPanel', () => { +describe.each([false, true])('LogsPanel with controls = %s', (showControls: boolean) => { it('publishes an event with the current sort order', async () => { publishMock.mockClear(); - setup(); + setup({}, showControls); await screen.findByText('logline text'); @@ -199,36 +199,52 @@ describe('LogsPanel', () => { ]; it('shows common labels when showCommonLabels is set to true', async () => { - setup({ - data: { ...defaultProps.data, series: seriesWithCommonLabels }, - options: { ...defaultProps.options, showCommonLabels: true }, - }); + setup( + { + data: { ...defaultProps.data, series: seriesWithCommonLabels }, + options: { ...defaultProps.options, showCommonLabels: true }, + }, + showControls + ); expect(await screen.findByText(/common labels:/i)).toBeInTheDocument(); expect(await screen.findByText(/common_app/i)).toBeInTheDocument(); expect(await screen.findByText(/common_job/i)).toBeInTheDocument(); }); it('shows common labels on top when descending sort order', async () => { - const { container } = setup({ - data: { ...defaultProps.data, series: seriesWithCommonLabels }, - options: { ...defaultProps.options, showCommonLabels: true, sortOrder: LogsSortOrder.Descending }, - }); + const { container } = setup( + { + data: { ...defaultProps.data, series: seriesWithCommonLabels }, + options: { ...defaultProps.options, showCommonLabels: true, sortOrder: LogsSortOrder.Descending }, + }, + showControls + ); expect(await screen.findByText(/common labels:/i)).toBeInTheDocument(); expect(container.firstChild?.childNodes[0].textContent).toMatch(/^Common labels:app=common_appjob=common_job/); }); it('shows common labels on bottom when ascending sort order', async () => { - const { container } = setup({ - data: { ...defaultProps.data, series: seriesWithCommonLabels }, - options: { ...defaultProps.options, showCommonLabels: true, sortOrder: LogsSortOrder.Ascending }, - }); + const { container } = setup( + { + data: { ...defaultProps.data, series: seriesWithCommonLabels }, + options: { ...defaultProps.options, showCommonLabels: true, sortOrder: LogsSortOrder.Ascending }, + }, + showControls + ); expect(await screen.findByText(/common labels:/i)).toBeInTheDocument(); - expect(container.firstChild?.childNodes[0].textContent).toMatch(/Common labels:app=common_appjob=common_job$/); + if (!showControls) { + expect(container.firstChild?.childNodes[0].textContent).toMatch(/Common labels:app=common_appjob=common_job$/); + } else { + expect(container.childNodes[0].textContent).toMatch(/Common labels:app=common_appjob=common_job$/); + } }); it('does not show common labels when showCommonLabels is set to false', async () => { - setup({ - data: { ...defaultProps.data, series: seriesWithCommonLabels }, - options: { ...defaultProps.options, showCommonLabels: false }, - }); + setup( + { + data: { ...defaultProps.data, series: seriesWithCommonLabels }, + options: { ...defaultProps.options, showCommonLabels: false }, + }, + showControls + ); await waitFor(async () => { expect(screen.queryByText(/common labels:/i)).toBeNull(); @@ -261,19 +277,25 @@ describe('LogsPanel', () => { }), ]; it('shows (no common labels) when showCommonLabels is set to true', async () => { - setup({ - data: { ...defaultProps.data, series: seriesWithoutCommonLabels }, - options: { ...defaultProps.options, showCommonLabels: true }, - }); + setup( + { + data: { ...defaultProps.data, series: seriesWithoutCommonLabels }, + options: { ...defaultProps.options, showCommonLabels: true }, + }, + showControls + ); expect(await screen.findByText(/common labels:/i)).toBeInTheDocument(); expect(await screen.findByText(/(no common labels)/i)).toBeInTheDocument(); }); it('does not show common labels when showCommonLabels is set to false', async () => { - setup({ - data: { ...defaultProps.data, series: seriesWithoutCommonLabels }, - options: { ...defaultProps.options, showCommonLabels: false }, - }); + setup( + { + data: { ...defaultProps.data, series: seriesWithoutCommonLabels }, + options: { ...defaultProps.options, showCommonLabels: false }, + }, + showControls + ); await waitFor(async () => { expect(screen.queryByText(/common labels:/i)).toBeNull(); expect(screen.queryByText(/(no common labels)/i)).toBeNull(); @@ -322,17 +344,20 @@ describe('LogsPanel', () => { }); it('should not show the toggle if the datasource does not support show context', async () => { - setup({ - data: { - ...defaultProps.data, - series, - request: { - ...defaultProps.data.request, - app: CoreApp.Dashboard, - targets: [{ refId: 'A', datasource: { uid: 'no-show-context' } }], + setup( + { + data: { + ...defaultProps.data, + series, + request: { + ...defaultProps.data.request, + app: CoreApp.Dashboard, + targets: [{ refId: 'A', datasource: { uid: 'no-show-context' } }], + }, }, }, - }); + showControls + ); await waitFor(async () => { await userEvent.hover(screen.getByText(/logline text/i)); @@ -341,17 +366,20 @@ describe('LogsPanel', () => { }); it('should show the toggle if the datasource does support show context', async () => { - setup({ - data: { - ...defaultProps.data, - series, - request: { - ...defaultProps.data.request, - app: CoreApp.Dashboard, - targets: [{ refId: 'A', datasource: { uid: 'show-context' } }], + setup( + { + data: { + ...defaultProps.data, + series, + request: { + ...defaultProps.data.request, + app: CoreApp.Dashboard, + targets: [{ refId: 'A', datasource: { uid: 'show-context' } }], + }, }, }, - }); + showControls + ); await waitFor(async () => { await userEvent.hover(screen.getByText(/logline text/i)); @@ -360,17 +388,20 @@ describe('LogsPanel', () => { }); it('should not show the toggle if the datasource does support show context but the app is not Dashboard', async () => { - setup({ - data: { - ...defaultProps.data, - series, - request: { - ...defaultProps.data.request, - app: CoreApp.CloudAlerting, - targets: [{ refId: 'A', datasource: { uid: 'show-context' } }], + setup( + { + data: { + ...defaultProps.data, + series, + request: { + ...defaultProps.data.request, + app: CoreApp.CloudAlerting, + targets: [{ refId: 'A', datasource: { uid: 'show-context' } }], + }, }, }, - }); + showControls + ); await waitFor(async () => { await userEvent.hover(screen.getByText(/logline text/i)); @@ -379,17 +410,20 @@ describe('LogsPanel', () => { }); it('should render the mocked `LogRowContextModal` after click', async () => { - setup({ - data: { - ...defaultProps.data, - series, - request: { - ...defaultProps.data.request, - app: CoreApp.Dashboard, - targets: [{ refId: 'A', datasource: { uid: 'show-context' } }], + setup( + { + data: { + ...defaultProps.data, + series, + request: { + ...defaultProps.data.request, + app: CoreApp.Dashboard, + targets: [{ refId: 'A', datasource: { uid: 'show-context' } }], + }, }, }, - }); + showControls + ); await waitFor(async () => { await userEvent.hover(screen.getByText(/logline text/i)); await userEvent.click(screen.getByLabelText(/show context/i)); @@ -398,17 +432,20 @@ describe('LogsPanel', () => { }); it('should call `getLogRowContext` if the user clicks the show context toggle', async () => { - setup({ - data: { - ...defaultProps.data, - series, - request: { - ...defaultProps.data.request, - app: CoreApp.Dashboard, - targets: [{ refId: 'A', datasource: { uid: 'show-context' } }], + setup( + { + data: { + ...defaultProps.data, + series, + request: { + ...defaultProps.data.request, + app: CoreApp.Dashboard, + targets: [{ refId: 'A', datasource: { uid: 'show-context' } }], + }, }, }, - }); + showControls + ); await waitFor(async () => { await userEvent.hover(screen.getByText(/logline text/i)); await userEvent.click(screen.getByLabelText(/show context/i)); @@ -427,17 +464,20 @@ describe('LogsPanel', () => { , ]; - setup({ - data: { - ...defaultProps.data, - series, + setup( + { + data: { + ...defaultProps.data, + series, + }, + options: { + ...defaultProps.options, + logRowMenuIconsBefore, + logRowMenuIconsAfter, + }, }, - options: { - ...defaultProps.options, - logRowMenuIconsBefore, - logRowMenuIconsAfter, - }, - }); + showControls + ); await waitFor(async () => { await userEvent.hover(screen.getByText(/logline text/i)); @@ -488,12 +528,15 @@ describe('LogsPanel', () => { }); it('does not rerender without changes', async () => { - const { rerender, props } = setup({ - data: { - ...defaultProps.data, - series, + const { rerender, props } = setup( + { + data: { + ...defaultProps.data, + series, + }, }, - }); + showControls + ); expect(await screen.findByRole('row')).toBeInTheDocument(); @@ -504,12 +547,15 @@ describe('LogsPanel', () => { }); it('rerenders when prop changes', async () => { - const { rerender, props } = setup({ - data: { - ...defaultProps.data, - series, + const { rerender, props } = setup( + { + data: { + ...defaultProps.data, + series, + }, }, - }); + showControls + ); expect(await screen.findByRole('row')).toBeInTheDocument(); @@ -520,12 +566,15 @@ describe('LogsPanel', () => { }); it('does not re-render when data is loading', async () => { - const { rerender, props } = setup({ - data: { - ...defaultProps.data, - series, + const { rerender, props } = setup( + { + data: { + ...defaultProps.data, + series, + }, }, - }); + showControls + ); expect(await screen.findByRole('row')).toBeInTheDocument(); @@ -571,26 +620,29 @@ describe('LogsPanel', () => { const filterForMock = jest.fn(); const filterOutMock = jest.fn(); const isFilterLabelActiveMock = jest.fn(); - setup({ - data: { - ...defaultProps.data, - series, + setup( + { + data: { + ...defaultProps.data, + series, + }, + options: { + ...defaultProps.options, + showLabels: false, + showTime: false, + wrapLogMessage: false, + showCommonLabels: false, + prettifyLogMessage: false, + sortOrder: LogsSortOrder.Descending, + dedupStrategy: LogsDedupStrategy.none, + enableLogDetails: true, + onClickFilterLabel: filterForMock, + onClickFilterOutLabel: filterOutMock, + isFilterLabelActive: isFilterLabelActiveMock, + }, }, - options: { - ...defaultProps.options, - showLabels: false, - showTime: false, - wrapLogMessage: false, - showCommonLabels: false, - prettifyLogMessage: false, - sortOrder: LogsSortOrder.Descending, - dedupStrategy: LogsDedupStrategy.none, - enableLogDetails: true, - onClickFilterLabel: filterForMock, - onClickFilterOutLabel: filterOutMock, - isFilterLabelActive: isFilterLabelActiveMock, - }, - }); + showControls + ); expect(await screen.findByRole('row')).toBeInTheDocument(); @@ -610,16 +662,19 @@ describe('LogsPanel', () => { eventBus: new EventBusSrv(), }); - setup({ - data: { - ...defaultProps.data, - series, + setup( + { + data: { + ...defaultProps.data, + series, + }, + options: { + ...defaultProps.options, + enableLogDetails: true, + }, }, - options: { - ...defaultProps.options, - enableLogDetails: true, - }, - }); + showControls + ); expect(await screen.findByRole('row')).toBeInTheDocument(); @@ -635,16 +690,19 @@ describe('LogsPanel', () => { onAddAdHocFilter: jest.fn(), }); - setup({ - data: { - ...defaultProps.data, - series, + setup( + { + data: { + ...defaultProps.data, + series, + }, + options: { + ...defaultProps.options, + enableLogDetails: true, + }, }, - options: { - ...defaultProps.options, - enableLogDetails: true, - }, - }); + showControls + ); expect(await screen.findByRole('row')).toBeInTheDocument(); @@ -690,26 +748,29 @@ describe('LogsPanel', () => { ]; it('displays the provided fields instead of the log line', async () => { - setup({ - data: { - ...defaultProps.data, - series, + setup( + { + data: { + ...defaultProps.data, + series, + }, + options: { + ...defaultProps.options, + showLabels: false, + showTime: false, + wrapLogMessage: false, + showCommonLabels: false, + prettifyLogMessage: false, + sortOrder: LogsSortOrder.Descending, + dedupStrategy: LogsDedupStrategy.none, + enableLogDetails: true, + displayedFields: ['app'], + onClickHideField: undefined, + onClickShowField: undefined, + }, }, - options: { - ...defaultProps.options, - showLabels: false, - showTime: false, - wrapLogMessage: false, - showCommonLabels: false, - prettifyLogMessage: false, - sortOrder: LogsSortOrder.Descending, - dedupStrategy: LogsDedupStrategy.none, - enableLogDetails: true, - displayedFields: ['app'], - onClickHideField: undefined, - onClickShowField: undefined, - }, - }); + showControls + ); expect(await screen.findByRole('row')).toBeInTheDocument(); expect(screen.queryByText('logline text')).not.toBeInTheDocument(); @@ -724,25 +785,28 @@ describe('LogsPanel', () => { }); it('updates the provided fields instead of the log line', async () => { - const { rerender, props } = setup({ - data: { - ...defaultProps.data, - series, + const { rerender, props } = setup( + { + data: { + ...defaultProps.data, + series, + }, + options: { + ...defaultProps.options, + showLabels: false, + showTime: false, + wrapLogMessage: false, + showCommonLabels: false, + prettifyLogMessage: false, + sortOrder: LogsSortOrder.Descending, + dedupStrategy: LogsDedupStrategy.none, + enableLogDetails: true, + onClickHideField: undefined, + onClickShowField: undefined, + }, }, - options: { - ...defaultProps.options, - showLabels: false, - showTime: false, - wrapLogMessage: false, - showCommonLabels: false, - prettifyLogMessage: false, - sortOrder: LogsSortOrder.Descending, - dedupStrategy: LogsDedupStrategy.none, - enableLogDetails: true, - onClickHideField: undefined, - onClickShowField: undefined, - }, - }); + showControls + ); expect(await screen.findByRole('row')).toBeInTheDocument(); expect(screen.getByText('logline text')).toBeInTheDocument(); @@ -753,26 +817,29 @@ describe('LogsPanel', () => { }); it('enables the behavior with a default implementation', async () => { - setup({ - data: { - ...defaultProps.data, - series, + setup( + { + data: { + ...defaultProps.data, + series, + }, + options: { + ...defaultProps.options, + showLabels: false, + showTime: false, + wrapLogMessage: false, + showCommonLabels: false, + prettifyLogMessage: false, + sortOrder: LogsSortOrder.Descending, + dedupStrategy: LogsDedupStrategy.none, + enableLogDetails: true, + displayedFields: [], + onClickHideField: undefined, + onClickShowField: undefined, + }, }, - options: { - ...defaultProps.options, - showLabels: false, - showTime: false, - wrapLogMessage: false, - showCommonLabels: false, - prettifyLogMessage: false, - sortOrder: LogsSortOrder.Descending, - dedupStrategy: LogsDedupStrategy.none, - enableLogDetails: true, - displayedFields: [], - onClickHideField: undefined, - onClickShowField: undefined, - }, - }); + showControls + ); expect(await screen.findByRole('row')).toBeInTheDocument(); @@ -789,25 +856,28 @@ describe('LogsPanel', () => { it('overrides the default implementation when the callbacks are provided', async () => { const onClickShowFieldMock = jest.fn(); - setup({ - data: { - ...defaultProps.data, - series, + setup( + { + data: { + ...defaultProps.data, + series, + }, + options: { + ...defaultProps.options, + showLabels: false, + showTime: false, + wrapLogMessage: false, + showCommonLabels: false, + prettifyLogMessage: false, + sortOrder: LogsSortOrder.Descending, + dedupStrategy: LogsDedupStrategy.none, + enableLogDetails: true, + onClickHideField: jest.fn(), + onClickShowField: onClickShowFieldMock, + }, }, - options: { - ...defaultProps.options, - showLabels: false, - showTime: false, - wrapLogMessage: false, - showCommonLabels: false, - prettifyLogMessage: false, - sortOrder: LogsSortOrder.Descending, - dedupStrategy: LogsDedupStrategy.none, - enableLogDetails: true, - onClickHideField: jest.fn(), - onClickShowField: onClickShowFieldMock, - }, - }); + showControls + ); expect(await screen.findByRole('row')).toBeInTheDocument(); @@ -819,7 +889,7 @@ describe('LogsPanel', () => { }); }); -const setup = (propsOverrides?: Partial) => { +const setup = (propsOverrides?: Partial, showControls = false) => { const props: LogsPanelProps = { ...defaultProps, data: { @@ -827,6 +897,7 @@ const setup = (propsOverrides?: Partial) => { }, options: { ...(propsOverrides?.options || defaultProps.options), + showControls, }, }; diff --git a/public/app/plugins/panel/logs/LogsPanel.tsx b/public/app/plugins/panel/logs/LogsPanel.tsx index be11edd4318..6b8f694122b 100644 --- a/public/app/plugins/panel/logs/LogsPanel.tsx +++ b/public/app/plugins/panel/logs/LogsPanel.tsx @@ -33,6 +33,7 @@ import { import { config, getAppEvents } from '@grafana/runtime'; import { ScrollContainer, usePanelContext, useStyles2 } from '@grafana/ui'; import { getFieldLinksForExplore } from 'app/features/explore/utils/links'; +import { ControlledLogRows } from 'app/features/logs/components/ControlledLogRows'; import { InfiniteScroll } from 'app/features/logs/components/InfiniteScroll'; import { LogRowContextModal } from 'app/features/logs/components/log-context/LogRowContextModal'; import { PanelDataErrorView } from 'app/features/panel/components/PanelDataErrorView'; @@ -45,6 +46,7 @@ import { COMMON_LABELS, dataFrameToLogsModel, dedupLogRows } from '../../../feat import { GetFieldLinksFn, + isCoreApp, isIsFilterLabelActive, isOnClickFilterLabel, isOnClickFilterOutLabel, @@ -52,6 +54,7 @@ import { isOnClickFilterString, isOnClickHideField, isOnClickShowField, + isOnLogOptionsChange, isOnNewLogsReceivedType, isReactNodeArray, onNewLogsReceivedType, @@ -93,6 +96,17 @@ interface LogsPanelProps extends PanelProps { * * Callback to be invoked when enableInfiniteScrolling and new logs have been received after an scroll event. * onNewLogsReceived?: (allLogs: DataFrame[], newLogs: DataFrame[]) => void; + * + * Log Controls props: + * + * Enables a sidebar with controls for scrolling, sort order, deduplication, filtering, timestamps, wrapping, etc. + * showControls?: boolean + * + * If controls are enabled, the component will use this key to store changes to the aforementioned options. + * controlsStorageKey?: string + * + * If controls are enabled, this function is called when a change is made in one of the options from the controls. + * onLogOptionsChange?: (option: keyof LogListControlOptions, value: string | boolean | string[]) => void; */ } interface LogsPermalinkUrlState { @@ -108,6 +122,8 @@ export const LogsPanel = ({ timeZone, fieldConfig, options: { + showControls, + controlsStorageKey, showLabels, showTime, wrapLogMessage, @@ -121,6 +137,7 @@ export const LogsPanel = ({ onClickFilterOutLabel, onClickFilterOutString, onClickFilterString, + onLogOptionsChange, isFilterLabelActive, logRowMenuIconsBefore, logRowMenuIconsAfter, @@ -145,7 +162,7 @@ export const LogsPanel = ({ // Prevents the scroll position to change when new data from infinite scrolling is received const keepScrollPositionRef = useRef(null); let closeCallback = useRef<() => void>(); - const { eventBus, onAddAdHocFilter } = usePanelContext(); + const { app, eventBus, onAddAdHocFilter } = usePanelContext(); useEffect(() => { getAppEvents().publish( @@ -465,62 +482,114 @@ export const LogsPanel = ({ getLogRowContextUi={getLogRowContextUi} /> )} - setScrollElement(scrollElement)}> -
+ {!showControls ? ( + setScrollElement(scrollElement)}> +
+ {showCommonLabels && !isAscending && renderCommonLabels()} + + + + {showCommonLabels && isAscending && renderCommonLabels()} +
+
+ ) : ( +
{showCommonLabels && !isAscending && renderCommonLabels()} - setScrollElement(scrollElement)} + visualisationType="logs" loading={infiniteScrolling} loadMoreLogs={enableInfiniteScrolling ? loadMoreLogs : undefined} range={data.timeRange} + logRows={logRows} + deduplicatedRows={deduplicatedRows} + dedupStrategy={dedupStrategy} + onClickFilterLabel={ + isOnClickFilterLabel(onClickFilterLabel) ? onClickFilterLabel : defaultOnClickFilterLabel + } + onClickFilterOutLabel={ + isOnClickFilterOutLabel(onClickFilterOutLabel) ? onClickFilterOutLabel : defaultOnClickFilterOutLabel + } + showContextToggle={showContextToggle} + showLabels={showLabels} + showTime={showTime} + enableLogDetails={enableLogDetails} + wrapLogMessage={wrapLogMessage} + prettifyLogMessage={prettifyLogMessage} timeZone={timeZone} - rows={logRows} - scrollElement={scrollElement} - sortOrder={sortOrder} - > - - + getFieldLinks={getFieldLinks} + logsSortOrder={sortOrder} + displayedFields={displayedFields} + onClickShowField={displayedFields !== undefined ? onClickShowField : undefined} + onClickHideField={displayedFields !== undefined ? onClickHideField : undefined} + app={isCoreApp(app) ? app : CoreApp.Dashboard} + onLogRowHover={onLogRowHover} + onOpenContext={onOpenContext} + onPermalinkClick={onPermalinkClick} + permalinkedRowId={getLogsPanelState()?.logs?.id ?? undefined} + scrollIntoView={scrollIntoView} + isFilterLabelActive={isIsFilterLabelActive(isFilterLabelActive) ? isFilterLabelActive : undefined} + onClickFilterString={isOnClickFilterString(onClickFilterString) ? onClickFilterString : undefined} + onClickFilterOutString={ + isOnClickFilterOutString(onClickFilterOutString) ? onClickFilterOutString : undefined + } + logRowMenuIconsBefore={isReactNodeArray(logRowMenuIconsBefore) ? logRowMenuIconsBefore : undefined} + logRowMenuIconsAfter={isReactNodeArray(logRowMenuIconsAfter) ? logRowMenuIconsAfter : undefined} + onLogOptionsChange={isOnLogOptionsChange(onLogOptionsChange) ? onLogOptionsChange : undefined} + logOptionsStorageKey={controlsStorageKey} + // Ascending order causes scroll to stick to the bottom, so previewing is futile + renderPreview={false} + /> {showCommonLabels && isAscending && renderCommonLabels()}
- + )} ); }; @@ -529,6 +598,9 @@ const getStyles = (theme: GrafanaTheme2) => ({ container: css({ marginBottom: theme.spacing(1.5), }), + controlledLogsContainer: css({ + height: '100%', + }), labelContainer: css({ margin: theme.spacing(0, 0, 0.5, 0.5), display: 'flex', diff --git a/public/app/plugins/panel/logs/panelcfg.cue b/public/app/plugins/panel/logs/panelcfg.cue index 341a6aed88c..b1efe883442 100644 --- a/public/app/plugins/panel/logs/panelcfg.cue +++ b/public/app/plugins/panel/logs/panelcfg.cue @@ -30,6 +30,8 @@ composableKinds: PanelCfg: { showCommonLabels: bool showTime: bool showLogContextToggle: bool + showControls?: bool + controlsStorageKey?: string wrapLogMessage: bool prettifyLogMessage: bool enableLogDetails: bool @@ -44,6 +46,7 @@ composableKinds: PanelCfg: { onClickFilterOutString?: _ onClickShowField?: _ onClickHideField?: _ + onLogOptionsChange?: _ logRowMenuIconsBefore?: _ logRowMenuIconsAfter?: _ onNewLogsReceived?: _ diff --git a/public/app/plugins/panel/logs/panelcfg.gen.ts b/public/app/plugins/panel/logs/panelcfg.gen.ts index 05df338fe83..74775a0ee61 100644 --- a/public/app/plugins/panel/logs/panelcfg.gen.ts +++ b/public/app/plugins/panel/logs/panelcfg.gen.ts @@ -11,6 +11,7 @@ import * as common from '@grafana/schema'; export interface Options { + controlsStorageKey?: string; dedupStrategy: common.LogsDedupStrategy; displayedFields?: Array; enableInfiniteScrolling?: boolean; @@ -27,9 +28,11 @@ export interface Options { onClickFilterString?: unknown; onClickHideField?: unknown; onClickShowField?: unknown; + onLogOptionsChange?: unknown; onNewLogsReceived?: unknown; prettifyLogMessage: boolean; showCommonLabels: boolean; + showControls?: boolean; showLabels: boolean; showLogContextToggle: boolean; showTime: boolean; diff --git a/public/app/plugins/panel/logs/types.test.ts b/public/app/plugins/panel/logs/types.test.ts new file mode 100644 index 00000000000..a6da9113bd9 --- /dev/null +++ b/public/app/plugins/panel/logs/types.test.ts @@ -0,0 +1,19 @@ +import { CoreApp } from '@grafana/data'; + +import { isCoreApp } from './types'; + +describe('isCoreApp', () => { + test('Identifies core apps', () => { + expect(isCoreApp(CoreApp.Explore)).toBe(true); + expect(isCoreApp(CoreApp.Unknown)).toBe(true); + expect(isCoreApp(CoreApp.PanelEditor)).toBe(true); + expect(isCoreApp(CoreApp.PanelViewer)).toBe(true); + expect(isCoreApp(CoreApp.Dashboard)).toBe(true); + }); + + test('Identifies non-apps', () => { + expect(isCoreApp('the explore')).toBe(false); + expect(isCoreApp('nope')).toBe(false); + expect(isCoreApp('drilldown')).toBe(false); + }); +}); diff --git a/public/app/plugins/panel/logs/types.ts b/public/app/plugins/panel/logs/types.ts index 758cd49dd88..9019d5750e8 100644 --- a/public/app/plugins/panel/logs/types.ts +++ b/public/app/plugins/panel/logs/types.ts @@ -1,6 +1,7 @@ import React, { ReactNode } from 'react'; -import { DataFrame, Field, LinkModel, ScopedVars } from '@grafana/data'; +import { CoreApp, DataFrame, Field, LinkModel, ScopedVars } from '@grafana/data'; +import { LogListControlOptions } from 'app/features/logs/components/panel/LogList'; export type { Options } from './panelcfg.gen'; @@ -12,6 +13,7 @@ type isFilterLabelActiveType = (key: string, value: string, refId?: string) => P type isOnClickShowFieldType = (value: string) => void; type isOnClickHideFieldType = (value: string) => void; export type onNewLogsReceivedType = (allLogs: DataFrame[], newLogs: DataFrame[]) => void; +type onLogOptionsChangeType = (option: keyof LogListControlOptions, value: string | boolean | string[]) => void; export type GetFieldLinksFn = ( field: Field, @@ -52,6 +54,15 @@ export function isOnNewLogsReceivedType(callback: unknown): callback is onNewLog return typeof callback === 'function'; } +export function isOnLogOptionsChange(callback: unknown): callback is onLogOptionsChangeType { + return typeof callback === 'function'; +} + export function isReactNodeArray(node: unknown): node is ReactNode[] { return Array.isArray(node) && node.every(React.isValidElement); } + +export function isCoreApp(app: unknown): app is CoreApp { + const apps = Object.values(CoreApp).map((coreApp) => coreApp.toString()); + return typeof app === 'string' && apps.includes(app); +} From 90ddab746118ff524dbf12e123294b9ef01f5369 Mon Sep 17 00:00:00 2001 From: Kim Nylander <104772500+knylander-grafana@users.noreply.github.com> Date: Wed, 23 Apr 2025 10:04:04 -0400 Subject: [PATCH 057/146] Fix link in Use git sync doc (#104396) --- .../provision-resources/file-path-setup.md | 2 +- .../observability-as-code/provision-resources/git-sync-setup.md | 2 +- .../observability-as-code/provision-resources/intro-git-sync.md | 2 +- .../provision-resources/provisioned-dashboards.md | 2 +- .../observability-as-code/provision-resources/use-git-sync.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/sources/observability-as-code/provision-resources/file-path-setup.md b/docs/sources/observability-as-code/provision-resources/file-path-setup.md index 9c8e8de5896..c559163ec35 100644 --- a/docs/sources/observability-as-code/provision-resources/file-path-setup.md +++ b/docs/sources/observability-as-code/provision-resources/file-path-setup.md @@ -24,7 +24,7 @@ Local file provisioning is an [experimental feature](https://grafana.com/docs/re - [Set up Git Sync](/docs/grafana//observability-as-code/provision-resources/git-sync-setup/) - [Set up file provisioning](/docs/grafana//observability-as-code/provision-resources/file-path-setup/) - [Work with provisioned dashboards](/docs/grafana//observability-as-code/provision-resources/provisioned-dashboards/) - - [Manage provisioned repositories with Git Sync](/docs/grafana/observability-as-code/provision-resources/use-git-sync/) + - [Manage provisioned repositories with Git Sync](/docs/grafana//observability-as-code/provision-resources/use-git-sync/)
diff --git a/docs/sources/observability-as-code/provision-resources/git-sync-setup.md b/docs/sources/observability-as-code/provision-resources/git-sync-setup.md index 31ae8a951a1..153604cc9d4 100644 --- a/docs/sources/observability-as-code/provision-resources/git-sync-setup.md +++ b/docs/sources/observability-as-code/provision-resources/git-sync-setup.md @@ -24,7 +24,7 @@ Git Sync is an [experimental feature](https://grafana.com/docs/release-life-cycl - [Set up Git Sync](/docs/grafana//observability-as-code/provision-resources/git-sync-setup/) - [Set up file provisioning](/docs/grafana//observability-as-code/provision-resources/file-path-setup/) - [Work with provisioned dashboards](/docs/grafana//observability-as-code/provision-resources/provisioned-dashboards/) - - [Manage provisioned repositories with Git Sync](/docs/grafana/observability-as-code/provision-resources/use-git-sync/) + - [Manage provisioned repositories with Git Sync](/docs/grafana//observability-as-code/provision-resources/use-git-sync/)
diff --git a/docs/sources/observability-as-code/provision-resources/intro-git-sync.md b/docs/sources/observability-as-code/provision-resources/intro-git-sync.md index 59dbf53695d..fadde370e2e 100644 --- a/docs/sources/observability-as-code/provision-resources/intro-git-sync.md +++ b/docs/sources/observability-as-code/provision-resources/intro-git-sync.md @@ -24,7 +24,7 @@ Git Sync is an [experimental feature](https://grafana.com/docs/release-life-cycl - [Set up Git Sync](/docs/grafana//observability-as-code/provision-resources/git-sync-setup/) - [Set up file provisioning](/docs/grafana//observability-as-code/provision-resources/file-path-setup/) - [Work with provisioned dashboards](/docs/grafana//observability-as-code/provision-resources/provisioned-dashboards/) - - [Manage provisioned repositories with Git Sync](/docs/grafana/observability-as-code/provision-resources/use-git-sync/) + - [Manage provisioned repositories with Git Sync](/docs/grafana//observability-as-code/provision-resources/use-git-sync/)
diff --git a/docs/sources/observability-as-code/provision-resources/provisioned-dashboards.md b/docs/sources/observability-as-code/provision-resources/provisioned-dashboards.md index 4b03d96ee61..7a4c8675bb8 100644 --- a/docs/sources/observability-as-code/provision-resources/provisioned-dashboards.md +++ b/docs/sources/observability-as-code/provision-resources/provisioned-dashboards.md @@ -24,7 +24,7 @@ Git Sync and File path provisioning an [experimental feature](https://grafana.co - [Set up Git Sync](/docs/grafana//observability-as-code/provision-resources/git-sync-setup/) - [Set up file provisioning](/docs/grafana//observability-as-code/provision-resources/file-path-setup/) - [Work with provisioned dashboards](/docs/grafana//observability-as-code/provision-resources/provisioned-dashboards/) - - [Manage provisioned repositories with Git Sync](/docs/grafana/observability-as-code/provision-resources/use-git-sync/) + - [Manage provisioned repositories with Git Sync](/docs/grafana//observability-as-code/provision-resources/use-git-sync/)
diff --git a/docs/sources/observability-as-code/provision-resources/use-git-sync.md b/docs/sources/observability-as-code/provision-resources/use-git-sync.md index 192a73226e1..100070ed9b9 100644 --- a/docs/sources/observability-as-code/provision-resources/use-git-sync.md +++ b/docs/sources/observability-as-code/provision-resources/use-git-sync.md @@ -23,7 +23,7 @@ weight: 400 - [Set up Git Sync](/docs/grafana//observability-as-code/provision-resources/git-sync-setup/) - [Set up file provisioning](/docs/grafana//observability-as-code/provision-resources/file-path-setup/) - [Work with provisioned dashboards](/docs/grafana//observability-as-code/provision-resources/provisioned-dashboards/) - - [Manage provisioned repositories with Git Sync](/docs/grafana/observability-as-code/provision-resources/use-git-sync/) + - [Manage provisioned repositories with Git Sync](/docs/grafana//observability-as-code/provision-resources/use-git-sync/)
From c8fb66dd4867bb0dfa8a3898e8b0e6884b645f2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 23 Apr 2025 16:18:56 +0200 Subject: [PATCH 058/146] Dashboard: Fixes issue with row repeats and first row (#104265) * working * Update * Add test * Update --- .../layout-default/RowRepeaterBehavior.test.tsx | 11 ++++++++++- .../scene/layout-default/RowRepeaterBehavior.ts | 17 +++++++++++------ yarn.lock | 16 ++++++++-------- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/public/app/features/dashboard-scene/scene/layout-default/RowRepeaterBehavior.test.tsx b/public/app/features/dashboard-scene/scene/layout-default/RowRepeaterBehavior.test.tsx index 28effcf18b5..c739b3d6520 100644 --- a/public/app/features/dashboard-scene/scene/layout-default/RowRepeaterBehavior.test.tsx +++ b/public/app/features/dashboard-scene/scene/layout-default/RowRepeaterBehavior.test.tsx @@ -61,7 +61,7 @@ describe('RowRepeaterBehavior', () => { const gridItemRow1 = row1.state.children[0] as SceneGridItem; expect(gridItemRow1.state.key!).toBe(joinCloneKeys(row1.state.key!, 'grid-item-1')); - expect(gridItemRow1.state.body?.state.key).toBe(joinCloneKeys(gridItemRow1.state.key!, 'canvas-1')); + expect(gridItemRow1.state.body?.state.key).toBe('canvas-1'); const row2 = grid.state.children[2] as SceneGridRow; expect(row2.state.key).toBe(getCloneKey('row-1', 1)); @@ -140,6 +140,9 @@ describe('RowRepeaterBehavior', () => { }); it('Should handle second repeat cycle and update remove old repeats', async () => { + const sourceRow = grid.state.children[1] as SceneGridRow; + const sourceGridItem = sourceRow.state.children[0] as SceneGridItem; + // trigger another repeat cycle by changing the variable const variable = scene.state.$variables!.state.variables[0] as TestVariable; variable.changeValueTo(['B1', 'C1']); @@ -148,6 +151,12 @@ describe('RowRepeaterBehavior', () => { // should now only have 2 repeated rows (and the panel above + the row at the bottom) expect(grid.state.children.length).toBe(4); + + // Should reuse source row item instances + const sourceRowAfterRepeat = grid.state.children[1] as SceneGridRow; + const sourceItemAfterRepeat = sourceRowAfterRepeat.state.children[0] as SceneGridItem; + expect(sourceRowAfterRepeat).toBe(sourceRow); + expect(sourceItemAfterRepeat).toBe(sourceGridItem); }); it('Should ignore repeat process if variable values are the same', async () => { diff --git a/public/app/features/dashboard-scene/scene/layout-default/RowRepeaterBehavior.ts b/public/app/features/dashboard-scene/scene/layout-default/RowRepeaterBehavior.ts index 5e7b4ef2f0e..975a7221119 100644 --- a/public/app/features/dashboard-scene/scene/layout-default/RowRepeaterBehavior.ts +++ b/public/app/features/dashboard-scene/scene/layout-default/RowRepeaterBehavior.ts @@ -26,8 +26,6 @@ import { import { getMultiVariableValues } from '../../utils/utils'; import { DashboardRepeatsProcessedEvent } from '../types/DashboardRepeatsProcessedEvent'; -import { DashboardGridItem } from './DashboardGridItem'; - interface RowRepeaterBehaviorState extends SceneObjectState { variableName: string; } @@ -196,15 +194,22 @@ export class RowRepeaterBehavior extends SceneObjectBase 0 + ? sourceItem.clone({ + isDraggable: false, + isResizable: false, + }) + : sourceItem; - const cloneItem = sourceItem.clone({ + cloneItem.setState({ key: cloneItemKey, y: cloneItemY, - isDraggable: !isSourceRow && sourceItem instanceof DashboardGridItem ? false : sourceItem.state.isDraggable, - isResizable: !isSourceRow && sourceItem instanceof DashboardGridItem ? false : sourceItem.state.isResizable, }); - ensureUniqueKeys(cloneItem, cloneItemKey); + if (rowIndex > 0) { + ensureUniqueKeys(cloneItem, cloneItemKey); + } children.push(cloneItem); diff --git a/yarn.lock b/yarn.lock index 70d29a1635c..42dd66568b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3475,10 +3475,10 @@ __metadata: linkType: soft "@grafana/scenes-react@npm:^6.8.1": - version: 6.8.1 - resolution: "@grafana/scenes-react@npm:6.8.1" + version: 6.9.0 + resolution: "@grafana/scenes-react@npm:6.9.0" dependencies: - "@grafana/scenes": "npm:6.8.1" + "@grafana/scenes": "npm:6.9.0" lru-cache: "npm:^10.2.2" react-use: "npm:^17.4.0" peerDependencies: @@ -3490,13 +3490,13 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/798595f91971beb5ba34989e7f6aabe8208c28011e36ca2da0f451bc112f760318a14c93d336ef08c99601ef386f25b70568e12c62c6e4a1f258bc93490dc111 + checksum: 10/c7c3759671f4497653a586e9396b5f89ac869dd30c9fb3077734ff80353fd612e2857136edc95d71c06607575930145af8d6811a088c9afda0299925c09ca9c7 languageName: node linkType: hard -"@grafana/scenes@npm:6.8.1, @grafana/scenes@npm:^6.8.1": - version: 6.8.1 - resolution: "@grafana/scenes@npm:6.8.1" +"@grafana/scenes@npm:6.9.0, @grafana/scenes@npm:^6.8.1": + version: 6.9.0 + resolution: "@grafana/scenes@npm:6.9.0" dependencies: "@floating-ui/react": "npm:^0.26.16" "@leeoniya/ufuzzy": "npm:^1.0.16" @@ -3514,7 +3514,7 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/e465eb864ab8a8c4a7dea1a90db1774630ee9d0cdff89799aaae5765c1eac313bd5e96ebb8dac51c41cdcaf5c78f46da6c7eeea18c4a506254990817c05aa840 + checksum: 10/ff02465c53877ce07d11f842696d00b0eb0945ca58d2586664429b66578c7743fb5ac715541649b69d52b2de8eef035340aa2df4cd0e8961c3b782eee9001551 languageName: node linkType: hard From ab7e18fedaab43060d8a67ea08eea6117b10d875 Mon Sep 17 00:00:00 2001 From: Alexa V <239999+axelavargas@users.noreply.github.com> Date: Wed, 23 Apr 2025 16:24:21 +0200 Subject: [PATCH 059/146] Dashboard: Schema V2 - Fix built-in annotations not present (#104313) * Add grafana Built-in annonation to the serialization * Add unit tests --- .../DashboardSceneSerializer.test.ts | 18 +++++++++++- .../transformSaveModelSchemaV2ToScene.test.ts | 25 +++++++++++----- .../transformSaveModelSchemaV2ToScene.ts | 29 +++++++++++++++++++ 3 files changed, 64 insertions(+), 8 deletions(-) diff --git a/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts b/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts index 084121cc917..20f42a76150 100644 --- a/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts +++ b/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts @@ -15,6 +15,7 @@ import { GridLayoutKind, PanelSpec, } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; +import { DEFAULT_ANNOTATION_COLOR } from '@grafana/ui'; import { AnnoKeyDashboardSnapshotOriginalUrl } from 'app/features/apiserver/types'; import { SaveDashboardAsOptions } from 'app/features/dashboard/components/SaveDashboard/types'; import { DASHBOARD_SCHEMA_VERSION } from 'app/features/dashboard/state/DashboardMigrator'; @@ -715,7 +716,22 @@ describe('DashboardSceneSerializer', () => { title: baseOptions.title, description: baseOptions.description, editable: true, - annotations: [], + annotations: [ + { + kind: 'AnnotationQuery', + spec: { + builtIn: true, + name: 'Annotations & Alerts', + datasource: { + uid: '-- Grafana --', + type: 'grafana', + }, + enable: true, + hide: true, + iconColor: DEFAULT_ANNOTATION_COLOR, + }, + }, + ], cursorSync: 'Off', liveNow: false, preload: false, diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts index 4a6116c044c..f356bbad15a 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.test.ts @@ -206,26 +206,36 @@ describe('transformSaveModelSchemaV2ToScene', () => { // Annotations expect(scene.state.$data).toBeInstanceOf(DashboardDataLayerSet); const dataLayers = scene.state.$data as DashboardDataLayerSet; + // we should get two annotations, Grafana built-in and the custom ones expect(dataLayers.state.annotationLayers).toHaveLength(dash.annotations.length); - expect(dataLayers.state.annotationLayers[0].state.name).toBe(dash.annotations[0].spec.name); - expect(dataLayers.state.annotationLayers[0].state.isEnabled).toBe(dash.annotations[0].spec.enable); - expect(dataLayers.state.annotationLayers[0].state.isHidden).toBe(dash.annotations[0].spec.hide); + expect(dataLayers.state.annotationLayers).toHaveLength(5); + + // Built-in + const builtInAnnotation = dataLayers.state.annotationLayers[0] as unknown as DashboardAnnotationsDataLayer; + expect(builtInAnnotation.state.name).toBe('Annotations & Alerts'); + expect(builtInAnnotation.state.isEnabled).toBe(true); + expect(builtInAnnotation.state.isHidden).toBe(true); + expect(builtInAnnotation.state?.query.builtIn).toBe(1); // Enabled expect(dataLayers.state.annotationLayers[1].state.name).toBe(dash.annotations[1].spec.name); expect(dataLayers.state.annotationLayers[1].state.isEnabled).toBe(dash.annotations[1].spec.enable); expect(dataLayers.state.annotationLayers[1].state.isHidden).toBe(dash.annotations[1].spec.hide); - // Disabled expect(dataLayers.state.annotationLayers[2].state.name).toBe(dash.annotations[2].spec.name); expect(dataLayers.state.annotationLayers[2].state.isEnabled).toBe(dash.annotations[2].spec.enable); expect(dataLayers.state.annotationLayers[2].state.isHidden).toBe(dash.annotations[2].spec.hide); - // Hidden + // Disabled expect(dataLayers.state.annotationLayers[3].state.name).toBe(dash.annotations[3].spec.name); expect(dataLayers.state.annotationLayers[3].state.isEnabled).toBe(dash.annotations[3].spec.enable); expect(dataLayers.state.annotationLayers[3].state.isHidden).toBe(dash.annotations[3].spec.hide); + // Hidden + expect(dataLayers.state.annotationLayers[4].state.name).toBe(dash.annotations[4].spec.name); + expect(dataLayers.state.annotationLayers[4].state.isEnabled).toBe(dash.annotations[4].spec.enable); + expect(dataLayers.state.annotationLayers[4].state.isHidden).toBe(dash.annotations[4].spec.hide); + // VizPanel const vizPanels = (scene.state.body as DashboardLayoutManager).getVizPanels(); expect(vizPanels).toHaveLength(3); @@ -757,9 +767,10 @@ describe('transformSaveModelSchemaV2ToScene', () => { // Get the annotation layers const dataLayerSet = scene.state.$data as DashboardDataLayerSet; expect(dataLayerSet).toBeDefined(); - expect(dataLayerSet.state.annotationLayers.length).toBe(1); + // it should have two annotation layers, built-in and custom + expect(dataLayerSet.state.annotationLayers.length).toBe(2); - const annotationLayer = dataLayerSet.state.annotationLayers[0] as DashboardAnnotationsDataLayer; + const annotationLayer = dataLayerSet.state.annotationLayers[1] as DashboardAnnotationsDataLayer; // Verify that the options have been merged into the query object expect(annotationLayer.state.query).toMatchObject({ diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts index c859ee6561e..d89c53dbd47 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts @@ -19,6 +19,7 @@ import { } from '@grafana/scenes'; import { AdhocVariableKind, + AnnotationQueryKind, ConstantVariableKind, CustomVariableKind, Spec as DashboardV2Spec, @@ -38,6 +39,7 @@ import { QueryVariableKind, TextVariableKind, } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; +import { DEFAULT_ANNOTATION_COLOR } from '@grafana/ui'; import { AnnoKeyCreatedBy, AnnoKeyFolder, @@ -87,6 +89,13 @@ export type TypedVariableModelV2 = export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo): DashboardScene { const { spec: dashboard, metadata } = dto; + // annotations might not come with the builtIn Grafana annotation, we need to add it + + const grafanaBuiltAnnotation = getGrafanaBuiltInAnnotationDataLayer(dashboard); + if (grafanaBuiltAnnotation) { + dashboard.annotations.unshift(grafanaBuiltAnnotation); + } + const annotationLayers = dashboard.annotations.map((annotation) => { let annoQuerySpec = annotation.spec; // some annotations will contain in the options properties that need to be @@ -500,3 +509,23 @@ export function getPanelElement(dashboard: DashboardV2Spec, elementName: string) export function getLibraryPanelElement(dashboard: DashboardV2Spec, elementName: string): LibraryPanelKind | undefined { return dashboard.elements[elementName].kind === 'LibraryPanel' ? dashboard.elements[elementName] : undefined; } +function getGrafanaBuiltInAnnotationDataLayer(dashboard: DashboardV2Spec) { + const found = dashboard.annotations.some((item) => item.spec.builtIn); + if (found) { + return; + } + + const grafanaBuiltAnnotation: AnnotationQueryKind = { + kind: 'AnnotationQuery', + spec: { + datasource: { uid: '-- Grafana --', type: 'grafana' }, + name: 'Annotations & Alerts', + iconColor: DEFAULT_ANNOTATION_COLOR, + enable: true, + hide: true, + builtIn: true, + }, + }; + + return grafanaBuiltAnnotation; +} From c20cd9874cf2975ae69c26f3ab4789ede1a44e02 Mon Sep 17 00:00:00 2001 From: Sven Grossmann Date: Wed, 23 Apr 2025 16:25:50 +0200 Subject: [PATCH 060/146] Extension Sidebar: Enable conditional rendering of component (#104177) * Extension Sidebar: Add `links` extension point to conditional render component * Extension Sidebar: Add tests * Extension Sidebar: Fix tests --- .../ExtensionSidebarProvider.test.tsx | 130 ++++++++++++++++-- .../ExtensionSidebarProvider.tsx | 36 ++++- .../ExtensionToolbarItem.test.tsx | 19 ++- 3 files changed, 167 insertions(+), 18 deletions(-) diff --git a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.test.tsx b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.test.tsx index 660aa81e014..b45be60bb2f 100644 --- a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.test.tsx +++ b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.test.tsx @@ -1,7 +1,7 @@ import { render, screen, act } from '@testing-library/react'; import { store, EventBusSrv, EventBus } from '@grafana/data'; -import { config, getAppEvents, setAppEvents } from '@grafana/runtime'; +import { config, getAppEvents, setAppEvents, locationService } from '@grafana/runtime'; import { getExtensionPointPluginMeta } from 'app/features/plugins/extensions/utils'; import { OpenExtensionSidebarEvent } from 'app/types/events'; @@ -13,6 +13,18 @@ import { EXTENSION_SIDEBAR_DOCKED_LOCAL_STORAGE_KEY, } from './ExtensionSidebarProvider'; +const mockComponent = { + title: 'Test Component', + description: 'Test Description', + targets: [], +}; + +const mockPluginMeta = { + pluginId: 'grafana-investigations-app', + addedComponents: [mockComponent], + addedLinks: [], +}; + // Mock the store jest.mock('@grafana/data', () => ({ ...jest.requireActual('@grafana/data'), @@ -38,23 +50,26 @@ jest.mock('@grafana/runtime', () => ({ extensionSidebar: true, }, }, + locationService: { + getLocation: jest.fn().mockReturnValue({ pathname: '/test-path' }), + getLocationObservable: jest.fn(), + }, + usePluginLinks: jest.fn().mockImplementation(() => ({ + links: [ + { + pluginId: mockPluginMeta.pluginId, + title: mockComponent.title, + }, + ], + })), })); -const mockComponent = { - title: 'Test Component', - description: 'Test Description', - targets: [], -}; - -const mockPluginMeta = { - pluginId: 'grafana-investigations-app', - addedComponents: [mockComponent], -}; - describe('ExtensionSidebarProvider', () => { let subscribeSpy: jest.SpyInstance; let originalAppEvents: EventBus; let mockEventBus: EventBusSrv; + let locationObservableMock: { callback: jest.Mock | null; subscribe: jest.Mock }; + const getExtensionPointPluginMetaMock = jest.mocked(getExtensionPointPluginMeta); beforeEach(() => { jest.clearAllMocks(); @@ -66,10 +81,21 @@ describe('ExtensionSidebarProvider', () => { setAppEvents(mockEventBus); - (getExtensionPointPluginMeta as jest.Mock).mockReturnValue(new Map([[mockPluginMeta.pluginId, mockPluginMeta]])); + getExtensionPointPluginMetaMock.mockReturnValue(new Map([[mockPluginMeta.pluginId, mockPluginMeta]])); jest.replaceProperty(config.featureToggles, 'extensionSidebar', true); + locationObservableMock = { + subscribe: jest.fn((callback) => { + locationObservableMock.callback = callback; + return { + unsubscribe: jest.fn(), + }; + }), + callback: null, + }; + (locationService.getLocationObservable as jest.Mock).mockReturnValue(locationObservableMock); + (store.get as jest.Mock).mockReturnValue(undefined); (store.set as jest.Mock).mockImplementation(() => {}); (store.delete as jest.Mock).mockImplementation(() => {}); @@ -196,14 +222,16 @@ describe('ExtensionSidebarProvider', () => { const permittedPluginMeta = { pluginId: 'grafana-investigations-app', addedComponents: [mockComponent], + addedLinks: [], }; const prohibitedPluginMeta = { pluginId: 'disabled-plugin', addedComponents: [mockComponent], + addedLinks: [], }; - (getExtensionPointPluginMeta as jest.Mock).mockReturnValue( + getExtensionPointPluginMetaMock.mockReturnValue( new Map([ [permittedPluginMeta.pluginId, permittedPluginMeta], [prohibitedPluginMeta.pluginId, prohibitedPluginMeta], @@ -328,6 +356,80 @@ describe('ExtensionSidebarProvider', () => { unmount(); expect(unsubscribeMock).toHaveBeenCalled(); }); + + it('should subscribe to location service observable', () => { + render( + + + + ); + + expect(locationService.getLocationObservable).toHaveBeenCalled(); + expect(locationObservableMock.subscribe).toHaveBeenCalled(); + }); + + it('should update current path when location changes', () => { + const usePluginLinksMock = jest.fn().mockReturnValue({ links: [] }); + jest.requireMock('@grafana/runtime').usePluginLinks = usePluginLinksMock; + + render( + + + + ); + + expect(usePluginLinksMock).toHaveBeenCalledWith( + expect.objectContaining({ + context: expect.objectContaining({ + path: '/test-path', + }), + }) + ); + + act(() => { + locationObservableMock.callback?.({ pathname: '/new-path' }); + }); + + expect(usePluginLinksMock).toHaveBeenCalledWith( + expect.objectContaining({ + context: expect.objectContaining({ + path: '/new-path', + }), + }) + ); + }); + + it('should unsubscribe from location service on unmount', () => { + const unsubscribeMock = jest.fn(); + locationObservableMock.subscribe.mockReturnValue({ + unsubscribe: unsubscribeMock, + }); + + const { unmount } = render( + + + + ); + + unmount(); + expect(unsubscribeMock).toHaveBeenCalled(); + }); + + it('should not include plugins in available components when no links are returned', () => { + jest.requireMock('@grafana/runtime').usePluginLinks.mockImplementation(() => ({ + links: [], + })); + + getExtensionPointPluginMetaMock.mockReturnValue(new Map([[mockPluginMeta.pluginId, mockPluginMeta]])); + + render( + + + + ); + + expect(screen.getByTestId('available-components-size')).toHaveTextContent('0'); + }); }); describe('Utility Functions', () => { diff --git a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx index 8f64c6a6071..df23d0f2898 100644 --- a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx +++ b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx @@ -2,7 +2,7 @@ import { createContext, ReactNode, useCallback, useContext, useEffect, useState import { useLocalStorage } from 'react-use'; import { store, type ExtensionInfo } from '@grafana/data'; -import { config, getAppEvents } from '@grafana/runtime'; +import { config, getAppEvents, usePluginLinks, locationService } from '@grafana/runtime'; import { ExtensionPointPluginMeta, getExtensionPointPluginMeta } from 'app/features/plugins/extensions/utils'; import { OpenExtensionSidebarEvent } from 'app/types/events'; @@ -75,13 +75,43 @@ export const ExtensionSidebarContextProvider = ({ children }: ExtensionSidebarCo EXTENSION_SIDEBAR_WIDTH_LOCAL_STORAGE_KEY, DEFAULT_EXTENSION_SIDEBAR_WIDTH ); + + const [currentPath, setCurrentPath] = useState(locationService.getLocation().pathname); + + useEffect(() => { + const subscription = locationService.getLocationObservable().subscribe((location) => { + setCurrentPath(location.pathname); + }); + + return () => { + subscription.unsubscribe(); + }; + }, []); + + // these links are needed to conditionally render the extension component + // that means, a plugin would need to register both, a link and a component to + // `grafana/extension-sidebar/v0-alpha` and the link's `configure` method would control + // whether the component is rendered or not + const { links } = usePluginLinks({ + extensionPointId: EXTENSION_SIDEBAR_EXTENSION_POINT_ID, + context: { + path: currentPath, + }, + }); + const isEnabled = !!config.featureToggles.extensionSidebar; // get all components for this extension point, but only for the permitted plugins // if the extension sidebar is not enabled, we will return an empty map const availableComponents = isEnabled ? new Map( - Array.from(getExtensionPointPluginMeta(EXTENSION_SIDEBAR_EXTENSION_POINT_ID).entries()).filter(([pluginId]) => - PERMITTED_EXTENSION_SIDEBAR_PLUGINS.includes(pluginId) + Array.from(getExtensionPointPluginMeta(EXTENSION_SIDEBAR_EXTENSION_POINT_ID).entries()).filter( + ([pluginId, pluginMeta]) => + PERMITTED_EXTENSION_SIDEBAR_PLUGINS.includes(pluginId) && + links.some( + (link) => + link.pluginId === pluginId && + pluginMeta.addedComponents.some((component) => component.title === link.title) + ) ) ) : new Map< diff --git a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionToolbarItem.test.tsx b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionToolbarItem.test.tsx index b1a6e35ed2c..2b468d9f4c2 100644 --- a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionToolbarItem.test.tsx +++ b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionToolbarItem.test.tsx @@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { EventBusSrv, store } from '@grafana/data'; -import { config, setAppEvents } from '@grafana/runtime'; +import { config, setAppEvents, usePluginLinks } from '@grafana/runtime'; import { getExtensionPointPluginMeta } from 'app/features/plugins/extensions/utils'; import { ExtensionSidebarContextProvider, useExtensionSidebarContext } from './ExtensionSidebarProvider'; @@ -33,6 +33,15 @@ jest.mock('@grafana/runtime', () => ({ extensionSidebar: true, }, }, + usePluginLinks: jest.fn().mockImplementation(() => ({ + links: [ + { + pluginId: mockPluginMeta.pluginId, + title: mockComponent.title, + }, + ], + isLoading: false, + })), })); const mockComponent = { @@ -121,6 +130,14 @@ describe('ExtensionToolbarItem', () => { ], }; + (usePluginLinks as jest.Mock).mockReturnValue({ + links: [ + { pluginId: multipleComponentsMeta.pluginId, title: multipleComponentsMeta.addedComponents[0].title }, + { pluginId: multipleComponentsMeta.pluginId, title: multipleComponentsMeta.addedComponents[1].title }, + ], + isLoading: false, + }); + (getExtensionPointPluginMeta as jest.Mock).mockReturnValue( new Map([[multipleComponentsMeta.pluginId, multipleComponentsMeta]]) ); From 5959dd3d180ba9368db8d6ca4f50cc56c65e1070 Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 14:37:58 +0000 Subject: [PATCH 061/146] Release: update changelog for 11.4.4 (#104365) * Update changelog * Update CHANGELOG.md --------- Co-authored-by: github-actions[bot] Co-authored-by: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com> --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23aeae4f688..0b565b1e084 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ + + +# 11.4.4 (2025-04-23) + +### Features and enhancements + +- **Go:** Bump to 1.24.2 (Enterprise) + +### Bug Fixes + +- **Security:** Fix CVE-2025-3454 +- **Security:** Fix CVE-2025-2703 + + # 11.3.6 (2025-04-22) From ee840b7d1f7dc28570721db649239a57c6bfe9a7 Mon Sep 17 00:00:00 2001 From: Collin Fingar Date: Wed, 23 Apr 2025 11:16:14 -0400 Subject: [PATCH 062/146] QueryLibrary: Update add to lib spec method (#103751) QueryLibrary: Update add to lib method --- public/app/features/explore/spec/helper/interactions.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/app/features/explore/spec/helper/interactions.ts b/public/app/features/explore/spec/helper/interactions.ts index dce38c66ef5..3e1d4f246fc 100644 --- a/public/app/features/explore/spec/helper/interactions.ts +++ b/public/app/features/explore/spec/helper/interactions.ts @@ -54,9 +54,9 @@ export const addQueryHistoryToQueryLibrary = async () => { await userEvent.click(button); }; -export const submitAddToQueryLibrary = async ({ description }: { description: string }) => { - const input = within(screen.getByRole('dialog')).getByLabelText('Description'); - await userEvent.type(input, description); +export const submitAddToQueryLibrary = async ({ title }: { title: string }) => { + const input = within(screen.getByRole('dialog')).getByLabelText('Title'); + await userEvent.type(input, title); const saveButton = screen.getByRole('button', { name: /^save$/i, }); From 0d400c341387d5c8ddbf1d30588481c762f1951a Mon Sep 17 00:00:00 2001 From: ismail simsek Date: Wed, 23 Apr 2025 17:16:40 +0200 Subject: [PATCH 063/146] Chore: Remove alignRange function from prometheus frontend as it was moved to backend (#104378) remove alignRange as it was moved to backend --- .../grafana-prometheus/src/datasource.test.ts | 41 ------------------- packages/grafana-prometheus/src/datasource.ts | 22 ---------- 2 files changed, 63 deletions(-) diff --git a/packages/grafana-prometheus/src/datasource.test.ts b/packages/grafana-prometheus/src/datasource.test.ts index 1e11c3b54d8..53a909d42db 100644 --- a/packages/grafana-prometheus/src/datasource.test.ts +++ b/packages/grafana-prometheus/src/datasource.test.ts @@ -19,7 +19,6 @@ import { import { config, getBackendSrv, setBackendSrv, TemplateSrv } from '@grafana/runtime'; import { - alignRange, extractRuleMappingFromGroups, PrometheusDatasource, prometheusRegularEscape, @@ -414,46 +413,6 @@ describe('PrometheusDatasource', () => { }); }); - describe('alignRange', () => { - it('does not modify already aligned intervals with perfect step', () => { - const range = alignRange(0, 3, 3, 0); - expect(range.start).toEqual(0); - expect(range.end).toEqual(3); - }); - - it('does modify end-aligned intervals to reflect number of steps possible', () => { - const range = alignRange(1, 6, 3, 0); - expect(range.start).toEqual(0); - expect(range.end).toEqual(6); - }); - - it('does align intervals that are a multiple of steps', () => { - const range = alignRange(1, 4, 3, 0); - expect(range.start).toEqual(0); - expect(range.end).toEqual(3); - }); - - it('does align intervals that are not a multiple of steps', () => { - const range = alignRange(1, 5, 3, 0); - expect(range.start).toEqual(0); - expect(range.end).toEqual(3); - }); - - it('does align intervals with local midnight -UTC offset', () => { - //week range, location 4+ hours UTC offset, 24h step time - const range = alignRange(4 * 60 * 60, (7 * 24 + 4) * 60 * 60, 24 * 60 * 60, -4 * 60 * 60); //04:00 UTC, 7 day range - expect(range.start).toEqual(4 * 60 * 60); - expect(range.end).toEqual((7 * 24 + 4) * 60 * 60); - }); - - it('does align intervals with local midnight +UTC offset', () => { - //week range, location 4- hours UTC offset, 24h step time - const range = alignRange(20 * 60 * 60, (8 * 24 - 4) * 60 * 60, 24 * 60 * 60, 4 * 60 * 60); //20:00 UTC on day1, 7 days later is 20:00 on day8 - expect(range.start).toEqual(20 * 60 * 60); - expect(range.end).toEqual((8 * 24 - 4) * 60 * 60); - }); - }); - describe('extractRuleMappingFromGroups()', () => { it('returns empty mapping for no rule groups', () => { expect(extractRuleMappingFromGroups([])).toEqual({}); diff --git a/packages/grafana-prometheus/src/datasource.ts b/packages/grafana-prometheus/src/datasource.ts index f871c4b626b..62f12169d37 100644 --- a/packages/grafana-prometheus/src/datasource.ts +++ b/packages/grafana-prometheus/src/datasource.ts @@ -1071,28 +1071,6 @@ export class PrometheusDatasource } } -/** - * Align query range to step. - * Rounds start and end down to a multiple of step. - * @param start Timestamp marking the beginning of the range. - * @param end Timestamp marking the end of the range. - * @param step Interval to align start and end with. - * @param utcOffsetSec Number of seconds current timezone is offset from UTC - */ -export function alignRange( - start: number, - end: number, - step: number, - utcOffsetSec: number -): { end: number; start: number } { - const alignedEnd = Math.floor((end + utcOffsetSec) / step) * step - utcOffsetSec; - const alignedStart = Math.floor((start + utcOffsetSec) / step) * step - utcOffsetSec; - return { - end: alignedEnd, - start: alignedStart, - }; -} - export function extractRuleMappingFromGroups(groups: RawRecordingRules[]): RuleQueryMapping { return groups.reduce( (mapping, group) => From 2e52553a3960c3c335d4ecfdde72438e4f31a253 Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 15:36:00 +0000 Subject: [PATCH 064/146] Release: update changelog for 11.5.4 (#104408) * Update changelog * Update CHANGELOG.md --------- Co-authored-by: github-actions[bot] Co-authored-by: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com> --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b565b1e084..15c82bba713 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,29 @@ + + +# 11.5.4 (2025-04-23) + +### Features and enhancements + +- **Azure Monitor:** Filter namespaces by resource group [#103654](https://github.com/grafana/grafana/pull/103654), [@alyssabull](https://github.com/alyssabull) +- **Azure:** Add support for custom namespace and custom metrics variable queries [#103650](https://github.com/grafana/grafana/pull/103650), [@aangelisc](https://github.com/aangelisc) +- **Azure:** Resource picker improvements [#103638](https://github.com/grafana/grafana/pull/103638), [@aangelisc](https://github.com/aangelisc) +- **Azure:** Support more complex variable interpolation [#103651](https://github.com/grafana/grafana/pull/103651), [@aangelisc](https://github.com/aangelisc) +- **Azure:** Variable editor and resource picker improvements [#103657](https://github.com/grafana/grafana/pull/103657), [@aangelisc](https://github.com/aangelisc) +- **Chore:** Update CVE-affected dependencies [#102709](https://github.com/grafana/grafana/pull/102709), [@grambbledook](https://github.com/grambbledook) +- **DashboardScenePage:** Correct slug in self referencing data links [#103853](https://github.com/grafana/grafana/pull/103853), [@Sergej-Vlasov](https://github.com/Sergej-Vlasov) +- **Dependencies:** Bump github.com/redis/go-redis/v9 to 9.6.3 to address CVE-2025-29923 [#102865](https://github.com/grafana/grafana/pull/102865), [@macabu](https://github.com/macabu) +- **Go:** Bump to 1.24.2 [#103525](https://github.com/grafana/grafana/pull/103525), [@Proximyst](https://github.com/Proximyst) +- **Go:** Bump to 1.24.2 (Enterprise) +- **Prometheus:** Add support for cloud partners Prometheus data sources [#103942](https://github.com/grafana/grafana/pull/103942), [@kevinwcyu](https://github.com/kevinwcyu) + +### Bug fixes + +- **InfluxDB:** Fix nested variable interpolation [#104095](https://github.com/grafana/grafana/pull/104095), [@aangelisc](https://github.com/aangelisc) +- **LDAP test:** Fix page crash [#102683](https://github.com/grafana/grafana/pull/102683), [@ashharrison90](https://github.com/ashharrison90) +- **Security:** Fix CVE-2025-3454 +- **Security:** Fix CVE-2025-2703 + + # 11.4.4 (2025-04-23) From b8eeea0fe0028f4960d966622f5df82719617f4d Mon Sep 17 00:00:00 2001 From: Tania <10127682+undef1nd@users.noreply.github.com> Date: Wed, 23 Apr 2025 18:21:13 +0200 Subject: [PATCH 065/146] Remove flag `disableSecretsCompatibility` (#103135) Remove flag disableSecretsCompatibility --- .../src/types/featureToggles.gen.ts | 4 - .../datasources/service/datasource.go | 16 +- pkg/services/featuremgmt/registry.go | 7 - pkg/services/featuremgmt/toggles_gen.csv | 1 - pkg/services/featuremgmt/toggles_gen.go | 4 - pkg/services/featuremgmt/toggles_gen.json | 796 ++++++------------ .../kvstore/migrations/datasource_mig.go | 28 +- .../kvstore/migrations/datasource_mig_test.go | 254 +----- 8 files changed, 259 insertions(+), 851 deletions(-) diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 54946660dd3..39b36fcfd76 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -59,10 +59,6 @@ export interface FeatureToggles { */ canvasPanelNesting?: boolean; /** - * Disable duplicated secret storage in legacy tables - */ - disableSecretsCompatibility?: boolean; - /** * Logs the path for requests that are instrumented as unknown */ logRequestsInstrumentedAsUnknown?: boolean; diff --git a/pkg/services/datasources/service/datasource.go b/pkg/services/datasources/service/datasource.go index 2f00a788c44..bbaa4ab0822 100644 --- a/pkg/services/datasources/service/datasource.go +++ b/pkg/services/datasources/service/datasource.go @@ -259,11 +259,9 @@ func (s *Service) AddDataSource(ctx context.Context, cmd *datasources.AddDataSou var err error cmd.EncryptedSecureJsonData = make(map[string][]byte) - if !s.features.IsEnabled(ctx, featuremgmt.FlagDisableSecretsCompatibility) { - cmd.EncryptedSecureJsonData, err = s.SecretsService.EncryptJsonData(ctx, cmd.SecureJsonData, secrets.WithoutScope()) - if err != nil { - return err - } + cmd.EncryptedSecureJsonData, err = s.SecretsService.EncryptJsonData(ctx, cmd.SecureJsonData, secrets.WithoutScope()) + if err != nil { + return err } cmd.UpdateSecretFn = func() error { @@ -948,11 +946,9 @@ func (s *Service) fillWithSecureJSONData(ctx context.Context, cmd *datasources.U } cmd.EncryptedSecureJsonData = make(map[string][]byte) - if !s.features.IsEnabled(ctx, featuremgmt.FlagDisableSecretsCompatibility) { - cmd.EncryptedSecureJsonData, err = s.SecretsService.EncryptJsonData(ctx, cmd.SecureJsonData, secrets.WithoutScope()) - if err != nil { - return err - } + cmd.EncryptedSecureJsonData, err = s.SecretsService.EncryptJsonData(ctx, cmd.SecureJsonData, secrets.WithoutScope()) + if err != nil { + return err } return nil diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 941781e000f..cba0275dcaa 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -86,13 +86,6 @@ var ( Owner: grafanaDatavizSquad, HideFromAdminPage: true, }, - { - Name: "disableSecretsCompatibility", - Description: "Disable duplicated secret storage in legacy tables", - Stage: FeatureStageExperimental, - RequiresRestart: true, - Owner: hostedGrafanaTeam, - }, { Name: "logRequestsInstrumentedAsUnknown", Description: "Logs the path for requests that are instrumented as unknown", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index bafd477aea4..9760fa856ff 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -8,7 +8,6 @@ featureHighlights,GA,@grafana/grafana-as-code,false,false,false storage,experimental,@grafana/search-and-storage,false,false,false correlations,GA,@grafana/dataviz-squad,false,false,false canvasPanelNesting,experimental,@grafana/dataviz-squad,false,false,true -disableSecretsCompatibility,experimental,@grafana/hosted-grafana-team,false,true,false logRequestsInstrumentedAsUnknown,experimental,@grafana/hosted-grafana-team,false,false,false grpcServer,preview,@grafana/search-and-storage,false,false,false cloudWatchCrossAccountQuerying,GA,@grafana/aws-datasources,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index d190c505986..3d2d2f8cfcb 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -43,10 +43,6 @@ const ( // Allow elements nesting FlagCanvasPanelNesting = "canvasPanelNesting" - // FlagDisableSecretsCompatibility - // Disable duplicated secret storage in legacy tables - FlagDisableSecretsCompatibility = "disableSecretsCompatibility" - // FlagLogRequestsInstrumentedAsUnknown // Logs the path for requests that are instrumented as unknown FlagLogRequestsInstrumentedAsUnknown = "logRequestsInstrumentedAsUnknown" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 29d5afd6581..ee03a1c27f8 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -6,7 +6,7 @@ { "metadata": { "name": "ABTestFeatureToggleA", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-01-13T21:13:13Z" }, "spec": { @@ -20,7 +20,7 @@ { "metadata": { "name": "ABTestFeatureToggleB", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-01-13T21:13:13Z" }, "spec": { @@ -34,7 +34,7 @@ { "metadata": { "name": "addFieldFromCalculationStatFunctions", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-11-03T14:39:58Z" }, "spec": { @@ -48,7 +48,7 @@ { "metadata": { "name": "aiGeneratedDashboardChanges", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-03-05T12:01:31Z" }, "spec": { @@ -61,7 +61,7 @@ { "metadata": { "name": "alertRuleRestore", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-03-05T14:15:26Z" }, "spec": { @@ -74,8 +74,8 @@ { "metadata": { "name": "alertRuleUseFiredAtForStartsAt", - "resourceVersion": "1744728881642", - "creationTimestamp": "2025-04-15T14:54:41Z" + "resourceVersion": "1745339544057", + "creationTimestamp": "2025-04-22T16:32:24Z" }, "spec": { "description": "Use FiredAt for StartsAt when sending alerts to Alertmaanger", @@ -84,64 +84,10 @@ "expression": "false" } }, - { - "metadata": { - "name": "alertStateHistoryLokiOnly", - "resourceVersion": "1743693517832", - "creationTimestamp": "2023-03-30T18:53:21Z", - "deletionTimestamp": "2025-04-08T13:50:27Z" - }, - "spec": { - "description": "Disable Grafana alerts from emitting annotations when a remote Loki instance is available.", - "stage": "experimental", - "codeowner": "@grafana/alerting-squad" - } - }, - { - "metadata": { - "name": "alertStateHistoryLokiPrimary", - "resourceVersion": "1743693517832", - "creationTimestamp": "2023-03-30T18:53:21Z", - "deletionTimestamp": "2025-04-08T13:50:27Z" - }, - "spec": { - "description": "Enable a remote Loki instance as the primary source for state history reads.", - "stage": "experimental", - "codeowner": "@grafana/alerting-squad" - } - }, - { - "metadata": { - "name": "alertStateHistoryLokiSecondary", - "resourceVersion": "1743693517832", - "creationTimestamp": "2023-03-30T18:53:21Z", - "deletionTimestamp": "2025-04-08T13:50:27Z" - }, - "spec": { - "description": "Enable Grafana to write alert state history to an external Loki instance in addition to Grafana annotations.", - "stage": "experimental", - "codeowner": "@grafana/alerting-squad" - } - }, - { - "metadata": { - "name": "alertingApiServer", - "resourceVersion": "1743693517832", - "creationTimestamp": "2024-06-20T20:52:03Z", - "deletionTimestamp": "2025-04-16T17:44:22Z" - }, - "spec": { - "description": "Register Alerting APIs with the K8s API server", - "stage": "GA", - "codeowner": "@grafana/alerting-squad", - "requiresRestart": true, - "expression": "true" - } - }, { "metadata": { "name": "alertingBacktesting", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2022-12-14T14:44:14Z" }, "spec": { @@ -153,7 +99,7 @@ { "metadata": { "name": "alertingCentralAlertHistory", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-05-29T15:01:38Z" }, "spec": { @@ -163,25 +109,10 @@ "frontend": true } }, - { - "metadata": { - "name": "alertingConversionAPI", - "resourceVersion": "1743693517832", - "creationTimestamp": "2025-02-12T07:13:21Z", - "deletionTimestamp": "2025-04-05T08:27:02Z" - }, - "spec": { - "description": "Enable the alerting conversion API", - "stage": "experimental", - "codeowner": "@grafana/alerting-squad", - "hideFromAdminPage": true, - "hideFromDocs": true - } - }, { "metadata": { "name": "alertingDisableSendAlertsExternal", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-05-23T12:29:19Z" }, "spec": { @@ -195,7 +126,7 @@ { "metadata": { "name": "alertingFilterV2", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-09-11T11:29:26Z" }, "spec": { @@ -208,7 +139,7 @@ { "metadata": { "name": "alertingInsights", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-09-14T12:58:04Z" }, "spec": { @@ -223,7 +154,7 @@ { "metadata": { "name": "alertingJiraIntegration", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-02-14T12:22:04Z" }, "spec": { @@ -237,11 +168,8 @@ { "metadata": { "name": "alertingListViewV2", - "resourceVersion": "1744700823766", - "creationTimestamp": "2024-05-24T14:40:49Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-04-15 07:07:03.766981 +0000 UTC" - } + "resourceVersion": "1745339544057", + "creationTimestamp": "2024-05-24T14:40:49Z" }, "spec": { "description": "Enables the new alert list view design", @@ -253,11 +181,8 @@ { "metadata": { "name": "alertingListViewV2PreviewToggle", - "resourceVersion": "1744700823766", - "creationTimestamp": "2025-04-14T13:28:02Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-04-15 07:07:03.766981 +0000 UTC" - } + "resourceVersion": "1745339544057", + "creationTimestamp": "2025-04-22T16:32:24Z" }, "spec": { "description": "Enables the alerting list view v2 preview toggle", @@ -269,7 +194,7 @@ { "metadata": { "name": "alertingMigrationUI", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-03-14T16:40:05Z" }, "spec": { @@ -284,7 +209,7 @@ { "metadata": { "name": "alertingNotificationsStepMode", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-11-22T11:07:45Z" }, "spec": { @@ -298,7 +223,7 @@ { "metadata": { "name": "alertingPrometheusRulesPrimary", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-09-27T12:27:16Z" }, "spec": { @@ -311,7 +236,7 @@ { "metadata": { "name": "alertingQueryAndExpressionsStepMode", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-09-26T06:33:14Z" }, "spec": { @@ -325,7 +250,7 @@ { "metadata": { "name": "alertingQueryOptimization", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-01-10T20:52:58Z" }, "spec": { @@ -338,7 +263,7 @@ { "metadata": { "name": "alertingRulePermanentlyDelete", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-04-03T11:18:25Z" }, "spec": { @@ -354,7 +279,7 @@ { "metadata": { "name": "alertingRuleRecoverDeleted", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-03-27T14:39:26Z" }, "spec": { @@ -370,7 +295,7 @@ { "metadata": { "name": "alertingRuleVersionHistoryRestore", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-02-17T12:25:32Z" }, "spec": { @@ -386,7 +311,7 @@ { "metadata": { "name": "alertingSaveStateCompressed", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-01-27T17:47:33Z" }, "spec": { @@ -399,7 +324,7 @@ { "metadata": { "name": "alertingSaveStatePeriodic", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-01-23T16:03:30Z" }, "spec": { @@ -411,7 +336,7 @@ { "metadata": { "name": "alertingSimplifiedRouting", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-11-10T13:14:39Z" }, "spec": { @@ -424,7 +349,7 @@ { "metadata": { "name": "alertingUIOptimizeReducer", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-11-18T10:59:00Z" }, "spec": { @@ -438,7 +363,7 @@ { "metadata": { "name": "alertmanagerRemoteOnly", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-10-30T16:27:08Z" }, "spec": { @@ -450,7 +375,7 @@ { "metadata": { "name": "alertmanagerRemotePrimary", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-10-30T16:27:08Z" }, "spec": { @@ -462,7 +387,7 @@ { "metadata": { "name": "alertmanagerRemoteSecondary", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-10-30T16:27:08Z" }, "spec": { @@ -474,7 +399,7 @@ { "metadata": { "name": "angularDeprecationUI", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-08-29T14:05:47Z" }, "spec": { @@ -488,7 +413,7 @@ { "metadata": { "name": "annotationPermissionUpdate", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-10-31T13:30:13Z" }, "spec": { @@ -501,7 +426,7 @@ { "metadata": { "name": "appPlatformGrpcClientAuth", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-10-14T10:47:18Z" }, "spec": { @@ -515,7 +440,7 @@ { "metadata": { "name": "assetSriChecks", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-03-04T10:56:35Z" }, "spec": { @@ -528,7 +453,7 @@ { "metadata": { "name": "authZGRPCServer", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-06-13T09:41:35Z" }, "spec": { @@ -542,7 +467,7 @@ { "metadata": { "name": "awsAsyncQueryCaching", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-07-21T15:34:07Z" }, "spec": { @@ -555,7 +480,7 @@ { "metadata": { "name": "awsDatasourcesTempCredentials", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-07-06T15:06:11Z" }, "spec": { @@ -567,7 +492,7 @@ { "metadata": { "name": "azureMonitorDisableLogLimit", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-10-24T13:32:09Z" }, "spec": { @@ -580,7 +505,7 @@ { "metadata": { "name": "azureMonitorEnableUserAuth", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-11-27T14:01:54Z" }, "spec": { @@ -593,7 +518,7 @@ { "metadata": { "name": "azureMonitorLogsBuilderEditor", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-04-02T14:15:25Z" }, "spec": { @@ -606,7 +531,7 @@ { "metadata": { "name": "azureMonitorPrometheusExemplars", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-06-06T16:53:17Z" }, "spec": { @@ -619,7 +544,7 @@ { "metadata": { "name": "cachingOptimizeSerializationMemoryUsage", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-10-12T16:56:49Z" }, "spec": { @@ -631,7 +556,7 @@ { "metadata": { "name": "canvasPanelNesting", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2022-05-31T19:03:34Z" }, "spec": { @@ -645,7 +570,7 @@ { "metadata": { "name": "canvasPanelPanZoom", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-01-02T19:52:21Z" }, "spec": { @@ -658,7 +583,7 @@ { "metadata": { "name": "cloudRBACRoles", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-01-10T13:19:01Z" }, "spec": { @@ -674,7 +599,7 @@ { "metadata": { "name": "cloudWatchBatchQueries", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-10-20T19:09:41Z" }, "spec": { @@ -686,7 +611,7 @@ { "metadata": { "name": "cloudWatchCrossAccountQuerying", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2022-11-28T11:39:12Z" }, "spec": { @@ -700,7 +625,7 @@ { "metadata": { "name": "cloudWatchNewLabelParsing", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-04-05T15:57:56Z" }, "spec": { @@ -713,7 +638,7 @@ { "metadata": { "name": "cloudWatchRoundUpEndTime", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-06-27T15:10:28Z" }, "spec": { @@ -726,7 +651,7 @@ { "metadata": { "name": "configurableSchedulerTick", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-07-26T16:44:12Z" }, "spec": { @@ -740,7 +665,7 @@ { "metadata": { "name": "correlations", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2022-09-16T13:14:27Z" }, "spec": { @@ -754,7 +679,7 @@ { "metadata": { "name": "crashDetection", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-11-12T15:07:27Z" }, "spec": { @@ -767,7 +692,7 @@ { "metadata": { "name": "dashboardDisableSchemaValidationV1", - "resourceVersion": "1744303023863", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-04-11T16:52:46Z" }, "spec": { @@ -779,7 +704,7 @@ { "metadata": { "name": "dashboardDisableSchemaValidationV2", - "resourceVersion": "1744303023863", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-04-11T16:52:46Z" }, "spec": { @@ -791,7 +716,7 @@ { "metadata": { "name": "dashboardNewLayouts", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-10-23T08:55:45Z" }, "spec": { @@ -804,7 +729,7 @@ { "metadata": { "name": "dashboardScene", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-11-13T08:51:21Z" }, "spec": { @@ -818,7 +743,7 @@ { "metadata": { "name": "dashboardSceneForViewers", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-11-02T19:02:25Z" }, "spec": { @@ -832,7 +757,7 @@ { "metadata": { "name": "dashboardSceneSolo", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-02-11T08:08:47Z" }, "spec": { @@ -846,7 +771,7 @@ { "metadata": { "name": "dashboardSchemaValidationLogging", - "resourceVersion": "1744303223631", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-04-11T16:52:46Z" }, "spec": { @@ -858,7 +783,7 @@ { "metadata": { "name": "dashgpt", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-08-30T20:22:05Z" }, "spec": { @@ -872,7 +797,7 @@ { "metadata": { "name": "dataplaneAggregator", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-08-09T08:41:07Z" }, "spec": { @@ -885,7 +810,7 @@ { "metadata": { "name": "dataplaneFrontendFallback", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-04-07T21:13:19Z" }, "spec": { @@ -900,7 +825,7 @@ { "metadata": { "name": "datasourceAPIServers", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-09-19T08:28:27Z" }, "spec": { @@ -913,7 +838,7 @@ { "metadata": { "name": "datasourceConnectionsTab", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-01-21T17:39:48Z" }, "spec": { @@ -926,7 +851,7 @@ { "metadata": { "name": "datasourceQueryTypes", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-05-23T16:46:28Z" }, "spec": { @@ -939,7 +864,7 @@ { "metadata": { "name": "disableClassicHTTPHistogram", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-06-18T19:37:44Z" }, "spec": { @@ -953,7 +878,7 @@ { "metadata": { "name": "disableEnvelopeEncryption", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2022-05-24T08:34:47Z" }, "spec": { @@ -967,7 +892,7 @@ { "metadata": { "name": "disableNumericMetricsSortingInExpressions", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-04-16T14:52:47Z" }, "spec": { @@ -980,7 +905,7 @@ { "metadata": { "name": "disableSSEDataplane", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-04-12T16:24:34Z" }, "spec": { @@ -989,23 +914,10 @@ "codeowner": "@grafana/observability-metrics" } }, - { - "metadata": { - "name": "disableSecretsCompatibility", - "resourceVersion": "1743693517832", - "creationTimestamp": "2022-07-12T20:27:37Z" - }, - "spec": { - "description": "Disable duplicated secret storage in legacy tables", - "stage": "experimental", - "codeowner": "@grafana/hosted-grafana-team", - "requiresRestart": true - } - }, { "metadata": { "name": "editPanelCSVDragAndDrop", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-01-24T09:43:44Z" }, "spec": { @@ -1018,7 +930,7 @@ { "metadata": { "name": "elasticsearchCrossClusterSearch", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-12-12T22:20:04Z" }, "spec": { @@ -1030,7 +942,7 @@ { "metadata": { "name": "elasticsearchImprovedParsing", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-01-15T17:05:54Z" }, "spec": { @@ -1042,7 +954,7 @@ { "metadata": { "name": "enableDatagridEditing", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-04-24T14:46:31Z" }, "spec": { @@ -1055,7 +967,7 @@ { "metadata": { "name": "enableExtensionsAdminPage", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-11-05T15:55:10Z" }, "spec": { @@ -1068,7 +980,7 @@ { "metadata": { "name": "enableNativeHTTPHistogram", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-10-03T18:23:55Z" }, "spec": { @@ -1082,7 +994,7 @@ { "metadata": { "name": "enableSCIM", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-11-07T14:38:46Z" }, "spec": { @@ -1094,7 +1006,7 @@ { "metadata": { "name": "enableScopesInMetricsExplore", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-11-06T13:11:33Z" }, "spec": { @@ -1108,7 +1020,7 @@ { "metadata": { "name": "exploreLogsAggregatedMetrics", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-08-29T13:55:59Z" }, "spec": { @@ -1121,7 +1033,7 @@ { "metadata": { "name": "exploreLogsLimitedTimeRange", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-08-29T13:55:59Z" }, "spec": { @@ -1134,7 +1046,7 @@ { "metadata": { "name": "exploreLogsShardSplitting", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-08-29T13:55:59Z" }, "spec": { @@ -1144,25 +1056,10 @@ "frontend": true } }, - { - "metadata": { - "name": "exploreMetrics", - "resourceVersion": "1743693517832", - "creationTimestamp": "2024-04-09T18:15:18Z", - "deletionTimestamp": "2025-04-11T20:45:14Z" - }, - "spec": { - "description": "Enables the new Grafana Metrics Drilldown core app", - "stage": "GA", - "codeowner": "@grafana/observability-metrics", - "frontend": true, - "expression": "true" - } - }, { "metadata": { "name": "exploreMetricsRelatedLogs", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-11-05T16:28:43Z" }, "spec": { @@ -1172,28 +1069,10 @@ "frontend": true } }, - { - "metadata": { - "name": "exploreMetricsUseExternalAppPlugin", - "resourceVersion": "1744146547481", - "creationTimestamp": "2025-02-03T20:46:54Z", - "deletionTimestamp": "2025-04-11T20:45:14Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-04-08 21:09:07.481769 +0000 UTC" - } - }, - "spec": { - "description": "Use the externalized Grafana Metrics Drilldown (formerly known as Explore Metrics) app plugin", - "stage": "GA", - "codeowner": "@grafana/observability-metrics", - "requiresRestart": true, - "expression": "true" - } - }, { "metadata": { "name": "expressionParser", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-02-17T00:59:11Z" }, "spec": { @@ -1206,7 +1085,7 @@ { "metadata": { "name": "extensionSidebar", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-04-03T10:16:35Z" }, "spec": { @@ -1219,7 +1098,7 @@ { "metadata": { "name": "externalCorePlugins", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-09-22T08:50:13Z" }, "spec": { @@ -1232,7 +1111,7 @@ { "metadata": { "name": "externalServiceAccounts", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-09-28T07:26:37Z" }, "spec": { @@ -1245,7 +1124,7 @@ { "metadata": { "name": "extraThemes", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-05-10T13:37:04Z" }, "spec": { @@ -1258,7 +1137,7 @@ { "metadata": { "name": "extractFieldsNameDeduplication", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-11-02T15:47:42Z" }, "spec": { @@ -1271,7 +1150,7 @@ { "metadata": { "name": "failWrongDSUID", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-06-20T10:56:39Z" }, "spec": { @@ -1284,7 +1163,7 @@ { "metadata": { "name": "faroDatasourceSelector", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-05-05T00:35:10Z" }, "spec": { @@ -1297,7 +1176,7 @@ { "metadata": { "name": "featureHighlights", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2022-02-03T11:53:23Z" }, "spec": { @@ -1311,7 +1190,7 @@ { "metadata": { "name": "featureToggleAdminPage", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-07-18T20:43:32Z" }, "spec": { @@ -1325,7 +1204,7 @@ { "metadata": { "name": "feedbackButton", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-12-02T17:08:15Z" }, "spec": { @@ -1338,7 +1217,7 @@ { "metadata": { "name": "fetchRulesUsingPost", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-01-29T12:17:44Z" }, "spec": { @@ -1349,36 +1228,10 @@ "hideFromDocs": true } }, - { - "metadata": { - "name": "flagDashboardDisableSchemaValidationV1", - "resourceVersion": "1744302136118", - "creationTimestamp": "2025-04-10T16:22:16Z", - "deletionTimestamp": "2025-04-10T16:37:03Z" - }, - "spec": { - "description": "Disable schema validation for dashboards/v1", - "stage": "experimental", - "codeowner": "@grafana/grafana-app-platform-squad" - } - }, - { - "metadata": { - "name": "flagDashboardDisableSchemaValidationV2", - "resourceVersion": "1744302136118", - "creationTimestamp": "2025-04-10T16:22:16Z", - "deletionTimestamp": "2025-04-10T16:37:03Z" - }, - "spec": { - "description": "Disable schema validation for dashboards/v2", - "stage": "experimental", - "codeowner": "@grafana/grafana-app-platform-squad" - } - }, { "metadata": { "name": "formatString", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-10-13T18:17:12Z" }, "spec": { @@ -1389,24 +1242,10 @@ "expression": "true" } }, - { - "metadata": { - "name": "frontendSandboxMonitorOnly", - "resourceVersion": "1743693517832", - "creationTimestamp": "2023-07-05T11:48:25Z", - "deletionTimestamp": "2025-04-14T13:27:59Z" - }, - "spec": { - "description": "Enables monitor only in the plugin frontend sandbox (if enabled)", - "stage": "privatePreview", - "codeowner": "@grafana/plugins-platform-backend", - "frontend": true - } - }, { "metadata": { "name": "grafanaAPIServerEnsureKubectlAccess", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-12-06T20:21:21Z" }, "spec": { @@ -1420,7 +1259,7 @@ { "metadata": { "name": "grafanaAPIServerWithExperimentalAPIs", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-10-06T18:55:22Z" }, "spec": { @@ -1434,7 +1273,7 @@ { "metadata": { "name": "grafanaAdvisor", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-01-20T10:08:00Z" }, "spec": { @@ -1446,7 +1285,7 @@ { "metadata": { "name": "grafanaManagedRecordingRules", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-04-22T17:53:16Z" }, "spec": { @@ -1460,7 +1299,7 @@ { "metadata": { "name": "grafanaManagedRecordingRulesDatasources", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-03-07T13:30:40Z" }, "spec": { @@ -1474,11 +1313,8 @@ { "metadata": { "name": "grafanaconThemes", - "resourceVersion": "1744293476104", - "creationTimestamp": "2025-02-06T11:08:04Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-04-10 13:57:56.104154 +0000 UTC" - } + "resourceVersion": "1745339544057", + "creationTimestamp": "2025-02-06T11:08:04Z" }, "spec": { "description": "Enables the temporary themes for GrafanaCon", @@ -1493,7 +1329,7 @@ { "metadata": { "name": "groupAttributeSync", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-09-09T15:29:43Z" }, "spec": { @@ -1506,7 +1342,7 @@ { "metadata": { "name": "groupByVariable", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-02-14T17:18:04Z" }, "spec": { @@ -1520,7 +1356,7 @@ { "metadata": { "name": "groupToNestedTableTransformation", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-02-07T14:28:26Z" }, "spec": { @@ -1534,7 +1370,7 @@ { "metadata": { "name": "grpcServer", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2022-09-26T20:25:34Z" }, "spec": { @@ -1547,7 +1383,7 @@ { "metadata": { "name": "homeSetupGuide", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-09-25T17:20:04Z" }, "spec": { @@ -1560,7 +1396,7 @@ { "metadata": { "name": "improvedExternalSessionHandling", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-09-17T10:54:39Z" }, "spec": { @@ -1573,7 +1409,7 @@ { "metadata": { "name": "improvedExternalSessionHandlingSAML", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-01-09T17:02:49Z" }, "spec": { @@ -1586,7 +1422,7 @@ { "metadata": { "name": "individualCookiePreferences", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-02-21T10:19:07Z" }, "spec": { @@ -1598,7 +1434,7 @@ { "metadata": { "name": "infinityRunQueriesInParallel", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-03-14T12:54:04Z" }, "spec": { @@ -1610,7 +1446,7 @@ { "metadata": { "name": "influxdbBackendMigration", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2022-02-09T18:26:16Z", "deletionTimestamp": "2023-01-17T14:11:26Z" }, @@ -1625,7 +1461,7 @@ { "metadata": { "name": "influxdbRunQueriesInParallel", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-02-01T10:58:24Z" }, "spec": { @@ -1637,7 +1473,7 @@ { "metadata": { "name": "influxqlStreamingParser", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-11-29T17:29:35Z" }, "spec": { @@ -1649,7 +1485,7 @@ { "metadata": { "name": "investigationsBackend", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-12-18T08:31:03Z" }, "spec": { @@ -1662,7 +1498,7 @@ { "metadata": { "name": "inviteUserExperimental", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-03-07T19:09:59Z" }, "spec": { @@ -1677,7 +1513,7 @@ { "metadata": { "name": "jaegerBackendMigration", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-11-15T14:40:20Z" }, "spec": { @@ -1689,7 +1525,7 @@ { "metadata": { "name": "jitterAlertRulesWithinGroups", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-01-18T18:48:11Z" }, "spec": { @@ -1703,7 +1539,7 @@ { "metadata": { "name": "k8SFolderCounts", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-12-27T17:10:44Z" }, "spec": { @@ -1716,7 +1552,7 @@ { "metadata": { "name": "k8SFolderMove", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-12-27T17:10:44Z" }, "spec": { @@ -1729,7 +1565,7 @@ { "metadata": { "name": "kubernetesAggregator", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-02-12T20:59:35Z" }, "spec": { @@ -1742,11 +1578,8 @@ { "metadata": { "name": "kubernetesClientDashboardsFolders", - "resourceVersion": "1744337414536", - "creationTimestamp": "2025-02-18T23:11:26Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-04-11 02:10:14.536012 +0000 UTC" - } + "resourceVersion": "1745339544057", + "creationTimestamp": "2025-02-18T23:11:26Z" }, "spec": { "description": "Route the folder and dashboard service requests to k8s", @@ -1758,7 +1591,7 @@ { "metadata": { "name": "kubernetesDashboards", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-06-05T14:34:23Z" }, "spec": { @@ -1771,7 +1604,7 @@ { "metadata": { "name": "kubernetesFeatureToggles", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-01-18T05:32:44Z" }, "spec": { @@ -1782,25 +1615,10 @@ "hideFromAdminPage": true } }, - { - "metadata": { - "name": "kubernetesPlaylists", - "resourceVersion": "1743693517832", - "creationTimestamp": "2023-10-05T19:00:36Z", - "deletionTimestamp": "2024-08-13T08:03:28Z" - }, - "spec": { - "description": "Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s", - "stage": "GA", - "codeowner": "@grafana/grafana-app-platform-squad", - "requiresRestart": true, - "expression": "true" - } - }, { "metadata": { "name": "kubernetesSnapshots", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-12-05T22:31:49Z" }, "spec": { @@ -1813,7 +1631,7 @@ { "metadata": { "name": "libraryPanelRBAC", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-10-11T23:30:50Z" }, "spec": { @@ -1823,24 +1641,10 @@ "requiresRestart": true } }, - { - "metadata": { - "name": "live-service-web-worker", - "resourceVersion": "1743693517832", - "creationTimestamp": "2022-01-26T17:44:20Z", - "deletionTimestamp": "2025-04-07T14:47:35Z" - }, - "spec": { - "description": "This will use a webworker thread to processes events rather than the main thread", - "stage": "experimental", - "codeowner": "@grafana/dashboards-squad", - "frontend": true - } - }, { "metadata": { "name": "localeFormatPreference", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-03-31T13:59:07Z" }, "spec": { @@ -1852,7 +1656,7 @@ { "metadata": { "name": "localizationForPlugins", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-03-31T04:38:38Z" }, "spec": { @@ -1864,7 +1668,7 @@ { "metadata": { "name": "logQLScope", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-11-11T11:53:24Z" }, "spec": { @@ -1879,7 +1683,7 @@ { "metadata": { "name": "logRequestsInstrumentedAsUnknown", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2022-06-10T08:56:55Z" }, "spec": { @@ -1891,7 +1695,7 @@ { "metadata": { "name": "logRowsPopoverMenu", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-11-16T09:48:10Z" }, "spec": { @@ -1905,7 +1709,7 @@ { "metadata": { "name": "logsContextDatasourceUi", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-01-27T14:12:01Z" }, "spec": { @@ -1920,7 +1724,7 @@ { "metadata": { "name": "logsExploreTableDefaultVisualization", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-05-02T15:28:15Z" }, "spec": { @@ -1933,7 +1737,7 @@ { "metadata": { "name": "logsExploreTableVisualisation", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-07-12T13:52:42Z" }, "spec": { @@ -1947,7 +1751,7 @@ { "metadata": { "name": "logsInfiniteScrolling", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-11-09T10:54:03Z" }, "spec": { @@ -1961,11 +1765,8 @@ { "metadata": { "name": "logsPanelControls", - "resourceVersion": "1744209818391", - "creationTimestamp": "2025-04-07T14:38:55Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-04-09 14:43:38.391331 +0000 UTC" - } + "resourceVersion": "1745339544057", + "creationTimestamp": "2025-04-07T14:38:55Z" }, "spec": { "description": "Enables a control component for the logs panel in Explore", @@ -1978,7 +1779,7 @@ { "metadata": { "name": "lokiExperimentalStreaming", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-06-19T10:03:51Z" }, "spec": { @@ -1990,7 +1791,7 @@ { "metadata": { "name": "lokiLabelNamesQueryApi", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-12-13T14:31:41Z" }, "spec": { @@ -2003,7 +1804,7 @@ { "metadata": { "name": "lokiLogsDataplane", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-07-13T07:58:00Z" }, "spec": { @@ -2015,7 +1816,7 @@ { "metadata": { "name": "lokiPredefinedOperations", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-06-02T10:52:36Z" }, "spec": { @@ -2028,7 +1829,7 @@ { "metadata": { "name": "lokiQueryHints", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-12-18T20:43:16Z" }, "spec": { @@ -2042,7 +1843,7 @@ { "metadata": { "name": "lokiQuerySplitting", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-02-09T17:27:02Z" }, "spec": { @@ -2057,7 +1858,7 @@ { "metadata": { "name": "lokiQuerySplittingConfig", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-03-20T15:51:36Z" }, "spec": { @@ -2070,7 +1871,7 @@ { "metadata": { "name": "lokiRunQueriesInParallel", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-09-19T09:34:01Z" }, "spec": { @@ -2082,7 +1883,7 @@ { "metadata": { "name": "lokiSendDashboardPanelNames", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-08-22T19:30:43Z" }, "spec": { @@ -2094,7 +1895,7 @@ { "metadata": { "name": "lokiShardSplitting", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-10-23T11:21:03Z" }, "spec": { @@ -2107,7 +1908,7 @@ { "metadata": { "name": "lokiStructuredMetadata", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-11-16T16:06:14Z" }, "spec": { @@ -2120,7 +1921,7 @@ { "metadata": { "name": "managedDualWriter", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-02-19T14:50:39Z" }, "spec": { @@ -2134,11 +1935,8 @@ { "metadata": { "name": "metricsFromProfiles", - "resourceVersion": "1743767250962", - "creationTimestamp": "2025-04-09T10:55:28Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-04-04 11:47:30.962546 +0000 UTC" - } + "resourceVersion": "1745339544057", + "creationTimestamp": "2025-04-09T10:55:28Z" }, "spec": { "description": "Enables creating metrics from profiles and storing them as recording rules", @@ -2150,7 +1948,7 @@ { "metadata": { "name": "mlExpressions", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-07-13T17:37:50Z" }, "spec": { @@ -2162,7 +1960,7 @@ { "metadata": { "name": "multiTenantTempCredentials", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-04-02T20:25:50Z" }, "spec": { @@ -2175,7 +1973,7 @@ { "metadata": { "name": "mysqlAnsiQuotes", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2022-10-12T11:43:35Z" }, "spec": { @@ -2187,7 +1985,7 @@ { "metadata": { "name": "nestedFolders", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2022-10-26T14:15:14Z" }, "spec": { @@ -2200,7 +1998,7 @@ { "metadata": { "name": "newDashboardSharingComponent", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-05-03T15:02:18Z" }, "spec": { @@ -2214,7 +2012,7 @@ { "metadata": { "name": "newDashboardWithFiltersAndGroupBy", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-04-04T11:25:21Z" }, "spec": { @@ -2228,7 +2026,7 @@ { "metadata": { "name": "newFiltersUI", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-08-30T12:48:13Z" }, "spec": { @@ -2241,7 +2039,7 @@ { "metadata": { "name": "newFolderPicker", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-01-15T11:43:19Z" }, "spec": { @@ -2254,7 +2052,7 @@ { "metadata": { "name": "newLogsPanel", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-02-04T17:40:17Z" }, "spec": { @@ -2267,7 +2065,7 @@ { "metadata": { "name": "newPDFRendering", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-02-08T12:09:34Z" }, "spec": { @@ -2280,7 +2078,7 @@ { "metadata": { "name": "newShareReportDrawer", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-02-17T19:05:46Z" }, "spec": { @@ -2291,42 +2089,10 @@ "hideFromDocs": true } }, - { - "metadata": { - "name": "noBackdropBlur", - "resourceVersion": "1744057771109", - "creationTimestamp": "2025-03-14T15:21:35Z", - "deletionTimestamp": "2025-04-08T10:58:19Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-04-07 20:29:31.109804663 +0000 UTC" - } - }, - "spec": { - "description": "Disables backdrop blur behind modals and drawers", - "stage": "GA", - "codeowner": "@grafana/grafana-frontend-platform", - "frontend": true, - "expression": "true" - } - }, - { - "metadata": { - "name": "nodeGraphDotLayout", - "resourceVersion": "1743693517832", - "creationTimestamp": "2024-01-31T16:26:12Z", - "deletionTimestamp": "2025-04-08T14:37:17Z" - }, - "spec": { - "description": "Changed the layout algorithm for the node graph", - "stage": "experimental", - "codeowner": "@grafana/observability-traces-and-profiling", - "frontend": true - } - }, { "metadata": { "name": "oauthRequireSubClaim", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-03-25T13:22:24Z" }, "spec": { @@ -2340,7 +2106,7 @@ { "metadata": { "name": "onPremToCloudMigrations", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-01-22T16:09:08Z" }, "spec": { @@ -2353,7 +2119,7 @@ { "metadata": { "name": "panelFilterVariable", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-11-03T12:15:54Z" }, "spec": { @@ -2367,7 +2133,7 @@ { "metadata": { "name": "panelMonitoring", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-10-09T05:19:08Z" }, "spec": { @@ -2381,7 +2147,7 @@ { "metadata": { "name": "panelTitleSearch", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2022-02-15T18:26:03Z" }, "spec": { @@ -2394,7 +2160,7 @@ { "metadata": { "name": "passwordlessMagicLinkAuthentication", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-11-14T13:50:55Z" }, "spec": { @@ -2408,7 +2174,7 @@ { "metadata": { "name": "pdfTables", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-11-06T13:39:22Z" }, "spec": { @@ -2420,7 +2186,7 @@ { "metadata": { "name": "permissionsFilterRemoveSubquery", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-08-02T07:39:25Z" }, "spec": { @@ -2432,7 +2198,7 @@ { "metadata": { "name": "pinNavItems", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-06-10T11:40:03Z" }, "spec": { @@ -2445,7 +2211,7 @@ { "metadata": { "name": "playlistsReconciler", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-12-20T03:09:31Z" }, "spec": { @@ -2458,7 +2224,7 @@ { "metadata": { "name": "pluginProxyPreserveTrailingSlash", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-06-05T11:36:14Z" }, "spec": { @@ -2468,25 +2234,11 @@ "expression": "false" } }, - { - "metadata": { - "name": "pluginsAPIMetrics", - "resourceVersion": "1743693517832", - "creationTimestamp": "2023-09-21T11:36:32Z", - "deletionTimestamp": "2025-04-14T09:21:29Z" - }, - "spec": { - "description": "Sends metrics of public grafana packages usage by plugins", - "stage": "experimental", - "codeowner": "@grafana/plugins-platform-backend", - "frontend": true - } - }, { "metadata": { "name": "pluginsAutoUpdate", - "resourceVersion": "1744729584489", - "creationTimestamp": "2025-04-15T15:06:24Z" + "resourceVersion": "1745339544057", + "creationTimestamp": "2025-04-22T16:32:24Z" }, "spec": { "description": "Enables auto-updating of users installed plugins", @@ -2497,7 +2249,7 @@ { "metadata": { "name": "pluginsCDNSyncLoader", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-02-07T10:07:08Z" }, "spec": { @@ -2509,7 +2261,7 @@ { "metadata": { "name": "pluginsDetailsRightPanel", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-08-13T09:55:30Z" }, "spec": { @@ -2523,7 +2275,7 @@ { "metadata": { "name": "pluginsFrontendSandbox", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-06-05T08:51:36Z" }, "spec": { @@ -2535,7 +2287,7 @@ { "metadata": { "name": "pluginsSkipHostEnvVars", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-11-15T17:09:14Z" }, "spec": { @@ -2547,7 +2299,7 @@ { "metadata": { "name": "pluginsSriChecks", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-10-04T12:55:09Z" }, "spec": { @@ -2560,7 +2312,7 @@ { "metadata": { "name": "preinstallAutoUpdate", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-11-07T12:14:25Z" }, "spec": { @@ -2573,7 +2325,7 @@ { "metadata": { "name": "preserveDashboardStateWhenNavigating", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-05-27T12:28:06Z" }, "spec": { @@ -2587,7 +2339,7 @@ { "metadata": { "name": "promQLScope", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-01-29T20:22:17Z" }, "spec": { @@ -2602,7 +2354,7 @@ { "metadata": { "name": "prometheusAzureOverrideAudience", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2022-05-30T15:43:32Z", "deletionTimestamp": "2023-07-16T21:30:14Z" }, @@ -2616,7 +2368,7 @@ { "metadata": { "name": "prometheusCodeModeMetricNamesSearch", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-04-04T20:38:23Z" }, "spec": { @@ -2626,24 +2378,10 @@ "frontend": true } }, - { - "metadata": { - "name": "prometheusRunQueriesInParallel", - "resourceVersion": "1743693517832", - "creationTimestamp": "2024-08-12T12:31:39Z", - "deletionTimestamp": "2025-04-11T22:11:19Z" - }, - "spec": { - "description": "Enables running Prometheus queries in parallel", - "stage": "GA", - "codeowner": "@grafana/oss-big-tent", - "expression": "true" - } - }, { "metadata": { "name": "prometheusSpecialCharsInLabelValues", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-12-18T21:31:08Z" }, "spec": { @@ -2670,7 +2408,7 @@ { "metadata": { "name": "provisioning", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-11-22T09:03:50Z" }, "spec": { @@ -2683,7 +2421,7 @@ { "metadata": { "name": "publicDashboardsEmailSharing", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-01-03T19:45:15Z" }, "spec": { @@ -2697,7 +2435,7 @@ { "metadata": { "name": "publicDashboardsScene", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-03-22T14:48:21Z" }, "spec": { @@ -2711,7 +2449,7 @@ { "metadata": { "name": "queryLibrary", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2022-10-07T18:31:45Z", "deletionTimestamp": "2023-03-20T16:00:14Z" }, @@ -2721,24 +2459,10 @@ "codeowner": "@grafana/grafana-frontend-platform" } }, - { - "metadata": { - "name": "queryOverLive", - "resourceVersion": "1743693517832", - "creationTimestamp": "2022-01-26T17:44:20Z", - "deletionTimestamp": "2025-04-07T14:47:35Z" - }, - "spec": { - "description": "Use Grafana Live WebSocket to execute backend queries", - "stage": "experimental", - "codeowner": "@grafana/dashboards-squad", - "frontend": true - } - }, { "metadata": { "name": "queryService", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-04-19T09:26:21Z" }, "spec": { @@ -2751,7 +2475,7 @@ { "metadata": { "name": "queryServiceFromExplore", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-04-02T10:00:33Z" }, "spec": { @@ -2764,7 +2488,7 @@ { "metadata": { "name": "queryServiceFromUI", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-04-19T09:26:21Z" }, "spec": { @@ -2777,7 +2501,7 @@ { "metadata": { "name": "queryServiceRewrite", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-04-19T09:26:21Z" }, "spec": { @@ -2790,7 +2514,7 @@ { "metadata": { "name": "recordedQueriesMulti", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-06-14T12:34:22Z" }, "spec": { @@ -2803,7 +2527,7 @@ { "metadata": { "name": "recoveryThreshold", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-10-10T14:51:50Z" }, "spec": { @@ -2817,7 +2541,7 @@ { "metadata": { "name": "refactorVariablesTimeRange", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-06-06T13:12:09Z" }, "spec": { @@ -2830,7 +2554,7 @@ { "metadata": { "name": "regressionTransformation", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-11-24T14:49:16Z" }, "spec": { @@ -2843,7 +2567,7 @@ { "metadata": { "name": "reloadDashboardsOnParamsChange", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-10-25T12:56:54Z" }, "spec": { @@ -2857,7 +2581,7 @@ { "metadata": { "name": "renderAuthJWT", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-04-03T16:53:38Z" }, "spec": { @@ -2870,7 +2594,7 @@ { "metadata": { "name": "rendererDisableAppPluginsPreload", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-02-24T14:43:06Z" }, "spec": { @@ -2885,7 +2609,7 @@ { "metadata": { "name": "reportingRetries", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-08-31T07:47:47Z" }, "spec": { @@ -2898,7 +2622,7 @@ { "metadata": { "name": "reportingUseRawTimeRange", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-11-14T20:08:03Z" }, "spec": { @@ -2911,7 +2635,7 @@ { "metadata": { "name": "rolePickerDrawer", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-09-26T12:51:38Z" }, "spec": { @@ -2923,7 +2647,7 @@ { "metadata": { "name": "scopeApi", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-11-27T07:58:25Z" }, "spec": { @@ -2937,7 +2661,7 @@ { "metadata": { "name": "scopeFilters", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-03-05T15:41:19Z" }, "spec": { @@ -2951,11 +2675,8 @@ { "metadata": { "name": "scopeSearchAllLevels", - "resourceVersion": "1744370973814", - "creationTimestamp": "2025-04-14T07:42:16Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-04-11 11:29:33.814419 +0000 UTC" - } + "resourceVersion": "1745339544057", + "creationTimestamp": "2025-04-14T07:42:16Z" }, "spec": { "description": "Enable scope search to include all levels of the scope node tree", @@ -2968,7 +2689,7 @@ { "metadata": { "name": "secretsManagementAppPlatform", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-03-19T09:25:14Z" }, "spec": { @@ -2980,7 +2701,7 @@ { "metadata": { "name": "showDashboardValidationWarnings", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2022-10-14T13:51:05Z" }, "spec": { @@ -2992,11 +2713,8 @@ { "metadata": { "name": "sqlDatasourceDatabaseSelection", - "resourceVersion": "1744189713698", - "creationTimestamp": "2023-06-06T16:28:52Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-04-09 09:08:33.698779 +0000 UTC" - } + "resourceVersion": "1745339544057", + "creationTimestamp": "2023-06-06T16:28:52Z" }, "spec": { "description": "Enables previous SQL data source dataset dropdown behavior", @@ -3009,11 +2727,8 @@ { "metadata": { "name": "sqlExpressions", - "resourceVersion": "1744213036999", - "creationTimestamp": "2024-02-27T21:16:00Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-04-09 15:37:16.999955611 +0000 UTC" - } + "resourceVersion": "1745339544057", + "creationTimestamp": "2024-02-27T21:16:00Z" }, "spec": { "description": "Enables SQL Expressions, which can execute SQL queries against data source results.", @@ -3024,7 +2739,7 @@ { "metadata": { "name": "sseGroupByDatasource", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-09-07T20:02:07Z" }, "spec": { @@ -3036,7 +2751,7 @@ { "metadata": { "name": "ssoSettingsApi", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-11-08T09:50:01Z" }, "spec": { @@ -3050,7 +2765,7 @@ { "metadata": { "name": "ssoSettingsLDAP", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-06-18T11:31:27Z" }, "spec": { @@ -3064,7 +2779,7 @@ { "metadata": { "name": "ssoSettingsSAML", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-03-14T11:04:45Z" }, "spec": { @@ -3078,7 +2793,7 @@ { "metadata": { "name": "storage", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2022-03-17T17:19:23Z" }, "spec": { @@ -3090,7 +2805,7 @@ { "metadata": { "name": "tableNextGen", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-03-26T03:57:57Z" }, "spec": { @@ -3102,7 +2817,7 @@ { "metadata": { "name": "tableSharedCrosshair", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-12-13T09:33:14Z" }, "spec": { @@ -3115,7 +2830,7 @@ { "metadata": { "name": "teamHttpHeadersMimir", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-01-13T10:42:47Z" }, "spec": { @@ -3128,7 +2843,7 @@ { "metadata": { "name": "templateVariablesUsesCombobox", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-01-31T09:53:13Z" }, "spec": { @@ -3141,7 +2856,7 @@ { "metadata": { "name": "timeRangeProvider", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-10-22T10:52:33Z" }, "spec": { @@ -3153,7 +2868,7 @@ { "metadata": { "name": "tlsMemcached", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-05-09T19:12:08Z" }, "spec": { @@ -3163,25 +2878,10 @@ "expression": "true" } }, - { - "metadata": { - "name": "traceQLStreaming", - "resourceVersion": "1743693517832", - "creationTimestamp": "2023-07-26T13:33:16Z", - "deletionTimestamp": "2025-04-09T12:57:07Z" - }, - "spec": { - "description": "Enables response streaming of TraceQL queries of the Tempo data source", - "stage": "GA", - "codeowner": "@grafana/observability-traces-and-profiling", - "frontend": true, - "expression": "false" - } - }, { "metadata": { "name": "transformationsRedesign", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-07-12T16:35:49Z" }, "spec": { @@ -3196,7 +2896,7 @@ { "metadata": { "name": "unifiedHistory", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-12-13T10:41:18Z" }, "spec": { @@ -3209,11 +2909,8 @@ { "metadata": { "name": "unifiedNavbars", - "resourceVersion": "1744174965165", - "creationTimestamp": "2025-04-09T12:51:22Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-04-09 05:02:45.165634 +0000 UTC" - } + "resourceVersion": "1745339544057", + "creationTimestamp": "2025-04-09T12:51:22Z" }, "spec": { "description": "Enables unified navbars", @@ -3226,7 +2923,7 @@ { "metadata": { "name": "unifiedRequestLog", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-03-31T13:38:09Z" }, "spec": { @@ -3240,7 +2937,7 @@ { "metadata": { "name": "unifiedStorageBigObjectsSupport", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-10-17T10:18:29Z" }, "spec": { @@ -3252,7 +2949,7 @@ { "metadata": { "name": "unifiedStorageGrpcConnectionPool", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-03-21T13:24:54Z" }, "spec": { @@ -3266,7 +2963,7 @@ { "metadata": { "name": "unifiedStorageHistoryPruner", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-03-17T10:36:38Z" }, "spec": { @@ -3280,7 +2977,7 @@ { "metadata": { "name": "unifiedStorageSearch", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-09-30T19:46:14Z" }, "spec": { @@ -3294,7 +2991,7 @@ { "metadata": { "name": "unifiedStorageSearchPermissionFiltering", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-01-22T11:38:37Z" }, "spec": { @@ -3309,7 +3006,7 @@ { "metadata": { "name": "unifiedStorageSearchSprinkles", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-12-18T17:00:54Z" }, "spec": { @@ -3323,7 +3020,7 @@ { "metadata": { "name": "unifiedStorageSearchUI", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-12-19T18:21:48Z" }, "spec": { @@ -3337,11 +3034,8 @@ { "metadata": { "name": "useScopesNavigationEndpoint", - "resourceVersion": "1744289349951", - "creationTimestamp": "2025-03-31T15:20:00Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-04-10 12:49:09.951013 +0000 UTC" - } + "resourceVersion": "1745339544057", + "creationTimestamp": "2025-03-31T15:20:00Z" }, "spec": { "description": "Use the scopes navigation endpoint instead of the dashboardbindings endpoint", @@ -3355,7 +3049,7 @@ { "metadata": { "name": "useSessionStorageForRedirection", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-09-23T09:31:23Z" }, "spec": { @@ -3368,7 +3062,7 @@ { "metadata": { "name": "wargamesTesting", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2023-09-13T18:32:01Z" }, "spec": { @@ -3380,7 +3074,7 @@ { "metadata": { "name": "xrayApplicationSignals", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2025-04-01T14:42:02Z" }, "spec": { @@ -3395,7 +3089,7 @@ { "metadata": { "name": "zanzana", - "resourceVersion": "1743693517832", + "resourceVersion": "1745339544057", "creationTimestamp": "2024-06-19T13:59:47Z" }, "spec": { diff --git a/pkg/services/secrets/kvstore/migrations/datasource_mig.go b/pkg/services/secrets/kvstore/migrations/datasource_mig.go index de09f26bc9a..c6b3a534e22 100644 --- a/pkg/services/secrets/kvstore/migrations/datasource_mig.go +++ b/pkg/services/secrets/kvstore/migrations/datasource_mig.go @@ -13,10 +13,8 @@ import ( const ( // Not set means migration has not happened secretMigrationStatusKey = "secretMigrationStatus" - // Migration happened with disableSecretCompatibility set to false + // Migration happened and secrets are stored in both locations compatibleSecretMigrationValue = "compatible" - // Migration happened with disableSecretCompatibility set to true - completeSecretMigrationValue = "complete" ) type DataSourceSecretMigrationService struct { @@ -43,17 +41,10 @@ func (s *DataSourceSecretMigrationService) Migrate(ctx context.Context) error { return err } logger.Debug(fmt.Sprint("secret migration status is ", migrationStatus)) - // If this flag is true, delete secrets from the legacy secrets store as they are migrated - disableSecretsCompatibility := s.features.IsEnabled(ctx, featuremgmt.FlagDisableSecretsCompatibility) - // If migration hasn't happened, migrate to unified secrets and keep copy in legacy - // If a complete migration happened and now backwards compatibility is enabled, copy secrets back to legacy - needCompatibility := migrationStatus != compatibleSecretMigrationValue && !disableSecretsCompatibility - // If migration hasn't happened, migrate to unified secrets and delete from legacy - // If a compatible migration happened and now compatibility is disabled, delete secrets from legacy - needMigration := migrationStatus != completeSecretMigrationValue && disableSecretsCompatibility - if needCompatibility || needMigration { - logger.Debug("performing secret migration", "needs migration", needMigration, "needs compatibility", needCompatibility) + // Only migrate if it hasn't happened yet + if migrationStatus != compatibleSecretMigrationValue { + logger.Debug("performing secret migration") query := &datasources.GetAllDataSourcesQuery{} dsList, err := s.dataSourcesService.GetAllDataSources(ctx, query) if err != nil { @@ -67,7 +58,6 @@ func (s *DataSourceSecretMigrationService) Migrate(ctx context.Context) error { } // Secrets are set by the update data source function if the SecureJsonData is set in the command - // Secrets are deleted by the update data source function if the disableSecretsCompatibility flag is enabled _, err = s.dataSourcesService.UpdateDataSource(ctx, &datasources.UpdateDataSourceCommand{ ID: ds.ID, OrgID: ds.OrgID, @@ -89,17 +79,11 @@ func (s *DataSourceSecretMigrationService) Migrate(ctx context.Context) error { } } - var newMigStatus string - if disableSecretsCompatibility { - newMigStatus = completeSecretMigrationValue - } else { - newMigStatus = compatibleSecretMigrationValue - } - err = s.kvStore.Set(ctx, secretMigrationStatusKey, newMigStatus) + err = s.kvStore.Set(ctx, secretMigrationStatusKey, compatibleSecretMigrationValue) if err != nil { return err } - logger.Debug(fmt.Sprint("set secret migration status to ", newMigStatus)) + logger.Debug(fmt.Sprint("set secret migration status to ", compatibleSecretMigrationValue)) } return nil diff --git a/pkg/services/secrets/kvstore/migrations/datasource_mig_test.go b/pkg/services/secrets/kvstore/migrations/datasource_mig_test.go index f22d2456eb7..f7a8429114f 100644 --- a/pkg/services/secrets/kvstore/migrations/datasource_mig_test.go +++ b/pkg/services/secrets/kvstore/migrations/datasource_mig_test.go @@ -30,13 +30,10 @@ func TestMain(m *testing.M) { testsuite.Run(m) } -func SetupTestDataSourceSecretMigrationService(t *testing.T, sqlStore db.DB, kvStore kvstore.KVStore, secretsStore secretskvs.SecretsKVStore, compatibility bool) *DataSourceSecretMigrationService { +func SetupTestDataSourceSecretMigrationService(t *testing.T, sqlStore db.DB, kvStore kvstore.KVStore, secretsStore secretskvs.SecretsKVStore) *DataSourceSecretMigrationService { t.Helper() cfg := &setting.Cfg{} features := featuremgmt.WithFeatures() - if !compatibility { - features = featuremgmt.WithFeatures(featuremgmt.FlagDisableSecretsCompatibility, true) - } secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore()) quotaService := quotatest.New(false, nil) dsService, err := dsservice.ProvideService(sqlStore, secretsService, secretsStore, cfg, features, acmock.New(), @@ -48,76 +45,12 @@ func SetupTestDataSourceSecretMigrationService(t *testing.T, sqlStore db.DB, kvS } func TestMigrate(t *testing.T) { - t.Run("should migrate from legacy to unified without compatibility", func(t *testing.T) { - sqlStore := db.InitTestDB(t) - kvStore := kvstore.ProvideService(sqlStore) - secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore()) - secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger")) - migService := SetupTestDataSourceSecretMigrationService(t, sqlStore, kvStore, secretsStore, false) - ds := dsservice.CreateStore(sqlStore, log.NewNopLogger()) - dataSourceName := "Test" - dataSourceOrg := int64(1) - _, err := ds.AddDataSource(context.Background(), &datasources.AddDataSourceCommand{ - OrgID: dataSourceOrg, - Name: dataSourceName, - Type: datasources.DS_MYSQL, - Access: datasources.DS_ACCESS_DIRECT, - URL: "http://test", - EncryptedSecureJsonData: map[string][]byte{ - "password": []byte("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"), - }, - }) - assert.NoError(t, err) - - // Check if the secret json data was added - query := &datasources.GetDataSourceQuery{OrgID: dataSourceOrg, Name: dataSourceName} - dataSource, err := ds.GetDataSource(context.Background(), query) - assert.NoError(t, err) - assert.NotNil(t, dataSource) - assert.NotEmpty(t, dataSource.SecureJsonData) - - // Check if the migration status key is empty - value, exist, err := kvStore.Get(context.Background(), 0, secretskvs.DataSourceSecretType, secretMigrationStatusKey) - assert.NoError(t, err) - assert.Empty(t, value) - assert.False(t, exist) - - // Check that the secret is not present on the secret store - value, exist, err = secretsStore.Get(context.Background(), dataSourceOrg, dataSourceName, secretskvs.DataSourceSecretType) - assert.NoError(t, err) - assert.Empty(t, value) - assert.False(t, exist) - - // Run the migration - err = migService.Migrate(context.Background()) - assert.NoError(t, err) - - // Check if the secure json data was deleted - query = &datasources.GetDataSourceQuery{OrgID: dataSourceOrg, Name: dataSourceName} - dataSource, err = ds.GetDataSource(context.Background(), query) - assert.NoError(t, err) - assert.NotNil(t, dataSource) - assert.Empty(t, dataSource.SecureJsonData) - - // Check if the secret was added to the secret store - value, exist, err = secretsStore.Get(context.Background(), dataSourceOrg, dataSourceName, secretskvs.DataSourceSecretType) - assert.NoError(t, err) - assert.NotEmpty(t, value) - assert.True(t, exist) - - // Check if the migration status key was set - value, exist, err = kvStore.Get(context.Background(), 0, secretskvs.DataSourceSecretType, secretMigrationStatusKey) - assert.NoError(t, err) - assert.Equal(t, completeSecretMigrationValue, value) - assert.True(t, exist) - }) - t.Run("should migrate from legacy to unified with compatibility", func(t *testing.T) { sqlStore := db.InitTestDB(t) kvStore := kvstore.ProvideService(sqlStore) secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore()) secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger")) - migService := SetupTestDataSourceSecretMigrationService(t, sqlStore, kvStore, secretsStore, true) + migService := SetupTestDataSourceSecretMigrationService(t, sqlStore, kvStore, secretsStore) ds := dsservice.CreateStore(sqlStore, log.NewNopLogger()) dataSourceName := "Test" dataSourceOrg := int64(1) @@ -177,187 +110,4 @@ func TestMigrate(t *testing.T) { assert.Equal(t, compatibleSecretMigrationValue, value) assert.True(t, exist) }) - - t.Run("should replicate from unified to legacy for compatibility", func(t *testing.T) { - sqlStore := db.InitTestDB(t) - - kvStore := kvstore.ProvideService(sqlStore) - secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore()) - secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger")) - migService := SetupTestDataSourceSecretMigrationService(t, sqlStore, kvStore, secretsStore, false) - ds := dsservice.CreateStore(sqlStore, log.NewNopLogger()) - - dataSourceName := "Test" - dataSourceOrg := int64(1) - - // Add test data source - _, err := ds.AddDataSource(context.Background(), &datasources.AddDataSourceCommand{ - OrgID: dataSourceOrg, - Name: dataSourceName, - Type: datasources.DS_MYSQL, - Access: datasources.DS_ACCESS_DIRECT, - URL: "http://test", - EncryptedSecureJsonData: map[string][]byte{ - "password": []byte("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"), - }, - }) - assert.NoError(t, err) - - // Check if the secret json data was added - query := &datasources.GetDataSourceQuery{OrgID: dataSourceOrg, Name: dataSourceName} - dataSource, err := ds.GetDataSource(context.Background(), query) - assert.NoError(t, err) - assert.NotNil(t, dataSource) - assert.NotEmpty(t, dataSource.SecureJsonData) - - // Check if the migration status key is empty - value, exist, err := kvStore.Get(context.Background(), 0, secretskvs.DataSourceSecretType, secretMigrationStatusKey) - assert.NoError(t, err) - assert.Empty(t, value) - assert.False(t, exist) - - // Check that the secret is not present on the secret store - value, exist, err = secretsStore.Get(context.Background(), dataSourceOrg, dataSourceName, secretskvs.DataSourceSecretType) - assert.NoError(t, err) - assert.Empty(t, value) - assert.False(t, exist) - - // Run the migration without compatibility - err = migService.Migrate(context.Background()) - assert.NoError(t, err) - - // Check if the secure json data was deleted - query = &datasources.GetDataSourceQuery{OrgID: dataSourceOrg, Name: dataSourceName} - dataSource, err = ds.GetDataSource(context.Background(), query) - assert.NoError(t, err) - assert.NotNil(t, dataSource) - assert.Empty(t, dataSource.SecureJsonData) - - // Check if the secret was added to the secret store - value, exist, err = secretsStore.Get(context.Background(), dataSourceOrg, dataSourceName, secretskvs.DataSourceSecretType) - assert.NoError(t, err) - assert.NotEmpty(t, value) - assert.True(t, exist) - - // Check if the migration status key was set - value, exist, err = kvStore.Get(context.Background(), 0, secretskvs.DataSourceSecretType, secretMigrationStatusKey) - assert.NoError(t, err) - assert.Equal(t, completeSecretMigrationValue, value) - assert.True(t, exist) - - // Run the migration with compatibility - migService = SetupTestDataSourceSecretMigrationService(t, sqlStore, kvStore, secretsStore, true) - err = migService.Migrate(context.Background()) - assert.NoError(t, err) - - // Check if the secure json data was re-added for compatibility - query = &datasources.GetDataSourceQuery{OrgID: dataSourceOrg, Name: dataSourceName} - dataSource, err = ds.GetDataSource(context.Background(), query) - assert.NoError(t, err) - assert.NotNil(t, dataSource) - assert.NotEmpty(t, dataSource.SecureJsonData) - - // Check if the secret was added to the secret store - value, exist, err = secretsStore.Get(context.Background(), dataSourceOrg, dataSourceName, secretskvs.DataSourceSecretType) - assert.NoError(t, err) - assert.NotEmpty(t, value) - assert.True(t, exist) - - // Check if the migration status key was set - value, exist, err = kvStore.Get(context.Background(), 0, secretskvs.DataSourceSecretType, secretMigrationStatusKey) - assert.NoError(t, err) - assert.Equal(t, compatibleSecretMigrationValue, value) - assert.True(t, exist) - }) - - t.Run("should delete from legacy to remove compatibility", func(t *testing.T) { - sqlStore := db.InitTestDB(t) - kvStore := kvstore.ProvideService(sqlStore) - secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore()) - secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger")) - migService := SetupTestDataSourceSecretMigrationService(t, sqlStore, kvStore, secretsStore, true) - ds := dsservice.CreateStore(sqlStore, log.NewNopLogger()) - - dataSourceName := "Test" - dataSourceOrg := int64(1) - - // Add test data source - _, err := ds.AddDataSource(context.Background(), &datasources.AddDataSourceCommand{ - OrgID: dataSourceOrg, - Name: dataSourceName, - Type: datasources.DS_MYSQL, - Access: datasources.DS_ACCESS_DIRECT, - URL: "http://test", - EncryptedSecureJsonData: map[string][]byte{ - "password": []byte("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"), - }, - }) - assert.NoError(t, err) - - // Check if the secret json data was added - query := &datasources.GetDataSourceQuery{OrgID: dataSourceOrg, Name: dataSourceName} - dataSource, err := ds.GetDataSource(context.Background(), query) - assert.NoError(t, err) - assert.NotNil(t, dataSource) - assert.NotEmpty(t, dataSource.SecureJsonData) - - // Check if the migration status key is empty - value, exist, err := kvStore.Get(context.Background(), 0, secretskvs.DataSourceSecretType, secretMigrationStatusKey) - assert.NoError(t, err) - assert.Empty(t, value) - assert.False(t, exist) - - // Check that the secret is not present on the secret store - value, exist, err = secretsStore.Get(context.Background(), dataSourceOrg, dataSourceName, secretskvs.DataSourceSecretType) - assert.NoError(t, err) - assert.Empty(t, value) - assert.False(t, exist) - - // Run the migration with compatibility - err = migService.Migrate(context.Background()) - assert.NoError(t, err) - - // Check if the secure json data was maintained for compatibility - query = &datasources.GetDataSourceQuery{OrgID: dataSourceOrg, Name: dataSourceName} - dataSource, err = ds.GetDataSource(context.Background(), query) - assert.NoError(t, err) - assert.NotNil(t, dataSource) - assert.NotEmpty(t, dataSource.SecureJsonData) - - // Check if the secret was added to the secret store - value, exist, err = secretsStore.Get(context.Background(), dataSourceOrg, dataSourceName, secretskvs.DataSourceSecretType) - assert.NoError(t, err) - assert.NotEmpty(t, value) - assert.True(t, exist) - - // Check if the migration status key was set - value, exist, err = kvStore.Get(context.Background(), 0, secretskvs.DataSourceSecretType, secretMigrationStatusKey) - assert.NoError(t, err) - assert.Equal(t, compatibleSecretMigrationValue, value) - assert.True(t, exist) - - // Run the migration without compatibility - migService = SetupTestDataSourceSecretMigrationService(t, sqlStore, kvStore, secretsStore, false) - err = migService.Migrate(context.Background()) - assert.NoError(t, err) - - // Check if the secure json data was deleted - query = &datasources.GetDataSourceQuery{OrgID: dataSourceOrg, Name: dataSourceName} - dataSource, err = ds.GetDataSource(context.Background(), query) - assert.NoError(t, err) - assert.NotNil(t, dataSource) - assert.Empty(t, dataSource.SecureJsonData) - - // Check if the secret was added to the secret store - value, exist, err = secretsStore.Get(context.Background(), dataSourceOrg, dataSourceName, secretskvs.DataSourceSecretType) - assert.NoError(t, err) - assert.NotEmpty(t, value) - assert.True(t, exist) - - // Check if the migration status key was set - value, exist, err = kvStore.Get(context.Background(), 0, secretskvs.DataSourceSecretType, secretMigrationStatusKey) - assert.NoError(t, err) - assert.Equal(t, completeSecretMigrationValue, value) - assert.True(t, exist) - }) } From 656d730eb2a39ced0619b56e8e655b862a827ae4 Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Wed, 23 Apr 2025 17:25:16 +0100 Subject: [PATCH 066/146] I18n: Remove 'now' translations (#104414) --- .../RelativeTimeRangePicker.tsx | 51 +------------------ public/app/core/utils/richHistory.ts | 4 +- .../DashboardSettings/TimePickerSettings.tsx | 2 +- public/locales/en-US/grafana.json | 10 +--- 4 files changed, 6 insertions(+), 61 deletions(-) diff --git a/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx index ad419960600..2c93cc5a506 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx @@ -14,7 +14,6 @@ import { Field } from '../../Forms/Field'; import { Icon } from '../../Icon/Icon'; import { getInputStyles, Input } from '../../Input/Input'; import { ScrollContainer } from '../../ScrollContainer/ScrollContainer'; -import { Tooltip } from '../../Tooltip/Tooltip'; import { TimePickerTitle } from '../TimeRangePicker/TimePickerTitle'; import { TimeRangeList } from '../TimeRangePicker/TimeRangeList'; import { quickOptions } from '../options'; @@ -170,13 +169,7 @@ export function RelativeTimeRangePicker(props: RelativeTimeRangePickerProps) {
- } placement="bottom" theme="info"> -
- - Specify time range - -
-
+ Specify time range
{ - const styles = useStyles2(toolTipStyles); - return ( - <> -
- - Supported formats: now-[digit]s/m/h/d/w - -
-
- - Example: to select a time range from 10 minutes ago to now - -
- - From: now-10m To: now - -
- - For more information see{' '} - - docs - - . - -
- - ); -}; - -const toolTipStyles = (theme: GrafanaTheme2) => ({ - supported: css({ - marginBottom: theme.spacing(1), - }), - tooltip: css({ - margin: 0, - }), - link: css({ - marginTop: theme.spacing(1), - }), -}); - const getStyles = (fromError?: string, toError?: string) => (theme: GrafanaTheme2) => { const inputStyles = getInputStyles({ theme, invalid: false }); const bodyMinimumHeight = 250; diff --git a/public/app/core/utils/richHistory.ts b/public/app/core/utils/richHistory.ts index 8816613a51b..7b261a13b06 100644 --- a/public/app/core/utils/richHistory.ts +++ b/public/app/core/utils/richHistory.ts @@ -202,8 +202,8 @@ export const createUrlFromRichHistory = (query: RichHistoryQuery) => { const exploreState: ExploreUrlState = { /* Default range, as we are not saving timerange in rich history */ range: { - from: t('explore.rich-history-utils.default-from', 'now-1h'), - to: t('explore.rich-history-utils.default-to', 'now'), + from: 'now-1h', + to: 'now', }, datasource: query.datasourceName, queries: query.queries, diff --git a/public/app/features/dashboard/components/DashboardSettings/TimePickerSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/TimePickerSettings.tsx index 03faa36a160..5072a676597 100644 --- a/public/app/features/dashboard/components/DashboardSettings/TimePickerSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/TimePickerSettings.tsx @@ -123,7 +123,7 @@ export class TimePickerSettings extends PureComponent { label={t('dashboard-settings.time-picker.refresh-live-dashboards-label', 'Refresh live dashboards')} description={t( 'dashboard-settings.time-picker.refresh-live-dashboards-description', - "Continuously re-draw panels where the time range references 'now'" + 'Continuously update panels when the time range includes the current time' )} > diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 1c8e02997c4..163e7a4ff60 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -4288,7 +4288,7 @@ "hide-time-picker": "Hide time picker", "now-delay-description": "Exclude recent data that may be incomplete.", "now-delay-label": "Now delay", - "refresh-live-dashboards-description": "Continuously re-draw panels where the time range references 'now'", + "refresh-live-dashboards-description": "Continuously update panels when the time range includes the current time", "refresh-live-dashboards-label": "Refresh live dashboards", "time-options-label": "Time options", "time-zone-label": "Time zone", @@ -4859,8 +4859,6 @@ "rich-history-utils": { "a-week-ago": "a week ago", "days-ago": "{{num}} days ago", - "default-from": "now-1h", - "default-to": "now", "today": "today", "two-weeks-ago": "two weeks ago", "yesterday": "yesterday" @@ -8522,15 +8520,11 @@ "apply": "Apply time range", "aria-role": "Time range selection", "default-title": "Time ranges", - "example": "Example: to select a time range from 10 minutes ago to now", - "example-details": "From: now-10m To: now", "example-title": "Example time ranges", "from-label": "From", "from-to": "{{timeOptionFrom}} to {{timeOptionTo}}", - "more-info": "For more information see <2>docs <1>.", - "specify": "Specify time range <1>", + "specify": "Specify time range", "submit-button-label": "TimePicker submit button", - "supported-formats": "Supported formats: <1>now-[digit]s/m/h/d/w", "to-label": "To" }, "zone": { From 345e81b9b7eeb2b56eab1be0d8a6a0fe899b11c4 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Wed, 23 Apr 2025 17:27:54 +0100 Subject: [PATCH 067/146] Alerting: Render duration in `` block for better presentation for translated values (#104409) --- public/app/features/alerting/unified/GrafanaRuleQueryViewer.tsx | 2 +- public/locales/en-US/grafana.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/features/alerting/unified/GrafanaRuleQueryViewer.tsx b/public/app/features/alerting/unified/GrafanaRuleQueryViewer.tsx index a594d312c16..781726d1413 100644 --- a/public/app/features/alerting/unified/GrafanaRuleQueryViewer.tsx +++ b/public/app/features/alerting/unified/GrafanaRuleQueryViewer.tsx @@ -127,7 +127,7 @@ export function QueryPreview({ i18nKey="alerting.query-preview.relative-time-range" values={{ from: rangeUtil.secondsToHms(relativeTimeRange.from) }} > - {'{{from}}'} to now + {'{{from}}'} to now ); diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 163e7a4ff60..a975f9585ed 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -1728,7 +1728,7 @@ "min-interval": "Min. Interval = {{minInterval}}" }, "query-preview": { - "relative-time-range": "{{from}} to now" + "relative-time-range": "<0>{{from}} to now" }, "queryAndExpressionsStep": { "disableAdvancedOptions": { From 7a9fdf8609fe86e1e124706f6029d1f9ab39c4e8 Mon Sep 17 00:00:00 2001 From: ismail simsek Date: Wed, 23 Apr 2025 18:57:02 +0200 Subject: [PATCH 068/146] Chore: Move getOriginalMetricName to the place where it's used (#104379) remove getOriginalMetricName from datasource as it's used only in metric_find_query --- packages/grafana-prometheus/src/datasource.ts | 6 +----- packages/grafana-prometheus/src/metric_find_query.ts | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/grafana-prometheus/src/datasource.ts b/packages/grafana-prometheus/src/datasource.ts index 62f12169d37..5f4a08b0b38 100644 --- a/packages/grafana-prometheus/src/datasource.ts +++ b/packages/grafana-prometheus/src/datasource.ts @@ -60,7 +60,7 @@ import { getQueryHints } from './query_hints'; import { promQueryModeller } from './querybuilder/PromQueryModeller'; import { QueryBuilderLabelFilter, QueryEditorMode } from './querybuilder/shared/types'; import { CacheRequestInfo, defaultPrometheusQueryOverlapWindow, QueryCache } from './querycache/QueryCache'; -import { getOriginalMetricName, transformV2 } from './result_transformer'; +import { transformV2 } from './result_transformer'; import { trackQuery } from './tracking'; import { ExemplarTraceIdDestination, @@ -904,10 +904,6 @@ export class PrometheusDatasource }; } - getOriginalMetricName(labelData: { [key: string]: string }) { - return getOriginalMetricName(labelData); - } - /** * This converts the adhocVariableFilter array and converts it to scopeFilter array * @param filters diff --git a/packages/grafana-prometheus/src/metric_find_query.ts b/packages/grafana-prometheus/src/metric_find_query.ts index ff506eb2f51..9bfe894ffeb 100644 --- a/packages/grafana-prometheus/src/metric_find_query.ts +++ b/packages/grafana-prometheus/src/metric_find_query.ts @@ -12,6 +12,7 @@ import { PrometheusMetricNamesRegex, PrometheusQueryResultRegex, } from './migrations/variableMigration'; +import { getOriginalMetricName } from './result_transformer'; import { escapeForUtf8Support, isValidLegacyName } from './utf8_support'; export class PrometheusMetricFindQuery { @@ -182,12 +183,11 @@ export class PrometheusMetricFindQuery { }; const url = `/api/v1/series`; - const self = this; return this.datasource.metadataRequest(url, params).then((result) => { return _map(result.data.data, (metric: { [key: string]: string }) => { return { - text: self.datasource.getOriginalMetricName(metric), + text: getOriginalMetricName(metric), expandable: true, }; }); From a6866176cd319055e31b2e58705bce4f0d5a39fb Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 17:34:58 +0000 Subject: [PATCH 069/146] Release: update changelog for 11.6.1 (#104416) * Update changelog * Update CHANGELOG.md --------- Co-authored-by: github-actions[bot] Co-authored-by: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com> --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15c82bba713..7239f14008c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,29 @@ + + +# 11.6.1 (2025-04-23) + +### Features and enhancements + +- **Chore:** Update JWT library (CVE-2025-30204) [#102727](https://github.com/grafana/grafana/pull/102727), [@grambbledook](https://github.com/grambbledook) +- **DashboardScenePage:** Correct slug in self referencing data links [#103854](https://github.com/grafana/grafana/pull/103854), [@Sergej-Vlasov](https://github.com/Sergej-Vlasov) +- **Dependencies:** Bump github.com/redis/go-redis/v9 to 9.7.3 to address CVE-2025-29923 [#102863](https://github.com/grafana/grafana/pull/102863), [@macabu](https://github.com/macabu) +- **Go:** Bump to 1.24.2 [#103523](https://github.com/grafana/grafana/pull/103523), [@Proximyst](https://github.com/Proximyst) +- **Go:** Bump to 1.24.2 (Enterprise) +- **GrafanaUI:** Use safePolygon close handler for interactive tooltips instead of a delay [#102869](https://github.com/grafana/grafana/pull/102869), [@mthorning](https://github.com/mthorning) +- **Prometheus:** Add support for cloud partners Prometheus data sources [#103941](https://github.com/grafana/grafana/pull/103941), [@kevinwcyu](https://github.com/kevinwcyu) + +### Bug fixes + +- **Alertmanager:** Add Role-Based Access Control via reqAction Field [#103479](https://github.com/grafana/grafana/pull/103479), [@olegpixel](https://github.com/olegpixel) +- **GrafanaUI:** Remove blurred background from overlay backdrops to improve performance [#103647](https://github.com/grafana/grafana/pull/103647), [@joshhunt](https://github.com/joshhunt) +- **InfluxDB:** Fix nested variable interpolation [#104096](https://github.com/grafana/grafana/pull/104096), [@aangelisc](https://github.com/aangelisc) +- **LDAP test:** Fix page crash [#102684](https://github.com/grafana/grafana/pull/102684), [@ashharrison90](https://github.com/ashharrison90) +- **Org redirection:** Fix linking between orgs [#102870](https://github.com/grafana/grafana/pull/102870), [@ashharrison90](https://github.com/ashharrison90) +- **Security:** Fix CVE-2025-3454 +- **Security:** Fix CVE-2025-2703 +- **Security:** Fix CVE-2025-3260 + + # 11.5.4 (2025-04-23) From b09d79b21cbaf28ddc19949345ce6f4054de5efd Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 23 Apr 2025 20:54:35 +0300 Subject: [PATCH 070/146] K8s/Dashboard: Promote from alpha1 to beta1 (#104009) --- Makefile | 12 +- apps/dashboard/Makefile | 12 +- apps/dashboard/kinds/dashboard.cue | 6 +- .../{v1alpha1 => v1beta1}/dashboard_spec.cue | 2 +- .../v1alpha1/zz_generated.conversion.go | 175 ------------------ .../{v1alpha1 => v1beta1}/constants.go | 4 +- .../dashboard_codec_gen.go | 2 +- .../{v1alpha1 => v1beta1}/dashboard_kind.cue | 0 .../dashboard_metadata_gen.go | 2 +- .../dashboard_object_gen.go | 2 +- .../dashboard_schema_gen.go | 4 +- .../{v1alpha1 => v1beta1}/dashboard_spec.go | 2 +- .../dashboard_spec_gen.go | 2 +- .../dashboard_status_gen.go | 2 +- .../dashboard/{v1alpha1 => v1beta1}/doc.go | 2 +- .../{v1alpha1 => v1beta1}/register.go | 4 +- .../dashboard/{v1alpha1 => v1beta1}/types.go | 2 +- .../{v1alpha1 => v1beta1}/validation.go | 3 +- .../v1beta1/zz_generated.conversion.go | 175 ++++++++++++++++++ .../zz_generated.deepcopy.go | 2 +- .../zz_generated.defaults.go | 2 +- .../zz_generated.openapi.go | 114 ++++++------ ...enerated.openapi_violation_exceptions.list | 4 +- apps/dashboard/pkg/apis/dashboard_manifest.go | 2 +- .../pkg/migration/conversion/conversion.go | 2 +- .../migration/conversion/conversion_test.go | 2 +- apps/dashboard/pkg/migration/migrate.go | 4 +- conf/provisioning/sample/dashboard-v1.json | 2 +- .../folder-A/folder-B/dashboard-nested.json | 2 +- .../dashboard_object_gen.ts | 0 .../types.metadata.gen.ts | 0 .../{v1alpha1 => v1beta1}/types.spec.gen.ts | 0 .../{v1alpha1 => v1beta1}/types.status.gen.ts | 0 pkg/api/dashboard.go | 5 +- pkg/api/dtos/dashboard.go | 2 +- .../datamigrations/to_unified_storage.go | 2 +- pkg/registry/apis/dashboard/large.go | 2 +- pkg/registry/apis/dashboard/large_test.go | 2 +- pkg/registry/apis/dashboard/legacy/migrate.go | 2 +- .../apis/dashboard/legacy/sql_dashboards.go | 57 +++--- .../dashboard/legacy/sql_dashboards_test.go | 33 ++-- pkg/registry/apis/dashboard/legacy/storage.go | 2 +- .../apis/dashboard/legacy/storage_test.go | 2 +- pkg/registry/apis/dashboard/legacy/types.go | 11 +- .../dashboard/legacysearcher/search_client.go | 2 +- .../legacysearcher/search_client_test.go | 2 +- pkg/registry/apis/dashboard/mutate.go | 8 +- pkg/registry/apis/dashboard/mutation_test.go | 15 +- pkg/registry/apis/dashboard/register.go | 2 +- pkg/registry/apis/dashboard/register_test.go | 15 +- .../apis/dashboard/schema_validation.go | 28 +-- pkg/registry/apis/dashboard/search.go | 5 +- .../provisioning/jobs/pullrequest/changes.go | 2 +- .../apis/provisioning/resources/client.go | 2 +- .../provisioning/resources/parser_test.go | 2 +- .../apiserver/standalone/runtime_test.go | 7 +- pkg/services/authz/zanzana/common/tuple.go | 6 +- pkg/services/authz/zanzana/translations.go | 2 +- .../dashboards/service/client/client.go | 2 +- .../dashboards/service/client/client_test.go | 2 +- .../service/dashboard_service_test.go | 2 +- pkg/services/folder/folderimpl/folder.go | 2 +- .../provisioning/dashboards/dashboard.go | 2 +- pkg/storage/legacysql/dualwrite/utils.go | 2 +- pkg/storage/legacysql/dualwrite/utils_test.go | 2 +- pkg/storage/unified/apistore/fake_large.go | 5 +- pkg/storage/unified/apistore/managed_test.go | 2 +- pkg/storage/unified/apistore/prepare_test.go | 2 +- pkg/storage/unified/resource/search.go | 2 +- pkg/storage/unified/search/dashboard.go | 2 +- pkg/tests/apis/dashboard/dashboards_test.go | 26 +-- .../integration/api_validation_test.go | 51 ++--- .../dashboard/testdata/dashboard-test-v1.yaml | 2 +- ...son => dashboard.grafana.app-v1beta1.json} | 136 +++++++------- pkg/tests/apis/openapi_test.go | 2 +- pkg/tests/apis/provisioning/client_test.go | 17 +- .../root_dashboard.json | 2 +- pkg/tests/apis/provisioning/helper_test.go | 2 +- .../DashboardScenePageStateManager.test.ts | 4 +- public/app/features/dashboard/api/types.ts | 2 +- public/app/features/dashboard/api/v1.test.ts | 4 +- public/app/features/dashboard/api/v1.ts | 2 +- public/app/features/dashboard/api/v2.test.ts | 4 +- public/app/features/dashboard/api/v2.ts | 1 + 84 files changed, 532 insertions(+), 517 deletions(-) rename apps/dashboard/kinds/{v1alpha1 => v1beta1}/dashboard_spec.cue (90%) delete mode 100644 apps/dashboard/pkg/apis/dashboard/v1alpha1/zz_generated.conversion.go rename apps/dashboard/pkg/apis/dashboard/{v1alpha1 => v1beta1}/constants.go (91%) rename apps/dashboard/pkg/apis/dashboard/{v1alpha1 => v1beta1}/dashboard_codec_gen.go (97%) rename apps/dashboard/pkg/apis/dashboard/{v1alpha1 => v1beta1}/dashboard_kind.cue (100%) rename apps/dashboard/pkg/apis/dashboard/{v1alpha1 => v1beta1}/dashboard_metadata_gen.go (98%) rename apps/dashboard/pkg/apis/dashboard/{v1alpha1 => v1beta1}/dashboard_object_gen.go (99%) rename apps/dashboard/pkg/apis/dashboard/{v1alpha1 => v1beta1}/dashboard_schema_gen.go (89%) rename apps/dashboard/pkg/apis/dashboard/{v1alpha1 => v1beta1}/dashboard_spec.go (93%) rename apps/dashboard/pkg/apis/dashboard/{v1alpha1 => v1beta1}/dashboard_spec_gen.go (75%) rename apps/dashboard/pkg/apis/dashboard/{v1alpha1 => v1beta1}/dashboard_status_gen.go (98%) rename apps/dashboard/pkg/apis/dashboard/{v1alpha1 => v1beta1}/doc.go (80%) rename apps/dashboard/pkg/apis/dashboard/{v1alpha1 => v1beta1}/register.go (98%) rename apps/dashboard/pkg/apis/dashboard/{v1alpha1 => v1beta1}/types.go (99%) rename apps/dashboard/pkg/apis/dashboard/{v1alpha1 => v1beta1}/validation.go (99%) create mode 100644 apps/dashboard/pkg/apis/dashboard/v1beta1/zz_generated.conversion.go rename apps/dashboard/pkg/apis/dashboard/{v1alpha1 => v1beta1}/zz_generated.deepcopy.go (99%) rename apps/dashboard/pkg/apis/dashboard/{v1alpha1 => v1beta1}/zz_generated.defaults.go (96%) rename apps/dashboard/pkg/apis/dashboard/{v1alpha1 => v1beta1}/zz_generated.openapi.go (84%) rename apps/dashboard/pkg/apis/dashboard/{v1alpha1 => v1beta1}/zz_generated.openapi_violation_exceptions.list (62%) rename packages/grafana-schema/src/schema/dashboard/{v1alpha1 => v1beta1}/dashboard_object_gen.ts (100%) rename packages/grafana-schema/src/schema/dashboard/{v1alpha1 => v1beta1}/types.metadata.gen.ts (100%) rename packages/grafana-schema/src/schema/dashboard/{v1alpha1 => v1beta1}/types.spec.gen.ts (100%) rename packages/grafana-schema/src/schema/dashboard/{v1alpha1 => v1beta1}/types.status.gen.ts (100%) rename pkg/tests/apis/openapi_snapshots/{dashboard.grafana.app-v1alpha1.json => dashboard.grafana.app-v1beta1.json} (95%) diff --git a/Makefile b/Makefile index 1219d1a9598..60763468e11 100644 --- a/Makefile +++ b/Makefile @@ -148,12 +148,12 @@ gen-cue: ## Do all CUE/Thema code generation @echo "generate code from .cue files" go generate ./kinds/gen.go go generate ./public/app/plugins/gen.go - @echo "// This file is managed by Grafana - DO NOT EDIT MANUALLY" > apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_kind.cue - @echo "// Source: kinds/dashboard/dashboard_kind.cue" >> apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_kind.cue - @echo "// To sync changes, run: make gen-cue" >> apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_kind.cue - @echo "" >> apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_kind.cue - @cat kinds/dashboard/dashboard_kind.cue >> apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_kind.cue - @cp apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_kind.cue apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_kind.cue + @echo "// This file is managed by Grafana - DO NOT EDIT MANUALLY" > apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue + @echo "// Source: kinds/dashboard/dashboard_kind.cue" >> apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue + @echo "// To sync changes, run: make gen-cue" >> apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue + @echo "" >> apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue + @cat kinds/dashboard/dashboard_kind.cue >> apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue + @cp apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_kind.cue .PHONY: gen-cuev2 diff --git a/apps/dashboard/Makefile b/apps/dashboard/Makefile index 3a05e034c79..7ea4489a97c 100644 --- a/apps/dashboard/Makefile +++ b/apps/dashboard/Makefile @@ -39,8 +39,8 @@ post-generate-cleanup: ## Clean up the generated code @cp ./tshack/v0alpha1_spec_gen.ts ../../packages/grafana-schema/src/schema/dashboard/v0alpha1/types.spec.gen.ts # Same for v1alpha1 - @rm ../../packages/grafana-schema/src/schema/dashboard/v1alpha1/types.spec.gen.ts - @cp ./tshack/v1alpha1_spec_gen.ts ../../packages/grafana-schema/src/schema/dashboard/v1alpha1/types.spec.gen.ts + @rm ../../packages/grafana-schema/src/schema/dashboard/v1beta1/types.spec.gen.ts + @cp ./tshack/v1alpha1_spec_gen.ts ../../packages/grafana-schema/src/schema/dashboard/v1beta1/types.spec.gen.ts # Remove auto-generated DeepCopy and DeepCopyInto methods for Spec for v0alpha1. @sed -e '/\/\/ DeepCopy creates a full deep copy of Spec/,+5d' ./pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go > ./pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go.tmp @@ -49,10 +49,10 @@ post-generate-cleanup: ## Clean up the generated code @mv ./pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go.tmp2 ./pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go # Remove auto-generated DeepCopy and DeepCopyInto methods for Spec for v1alpha1. - @sed -e '/\/\/ DeepCopy creates a full deep copy of Spec/,+5d' ./pkg/apis/dashboard/v1alpha1/dashboard_object_gen.go > ./pkg/apis/dashboard/v1alpha1/dashboard_object_gen.go.tmp - @sed -e '/\/\/ DeepCopyInto deep copies Spec into another Spec object/,+3d' ./pkg/apis/dashboard/v1alpha1/dashboard_object_gen.go.tmp > ./pkg/apis/dashboard/v1alpha1/dashboard_object_gen.go.tmp2 - @rm ./pkg/apis/dashboard/v1alpha1/dashboard_object_gen.go.tmp - @mv ./pkg/apis/dashboard/v1alpha1/dashboard_object_gen.go.tmp2 ./pkg/apis/dashboard/v1alpha1/dashboard_object_gen.go + @sed -e '/\/\/ DeepCopy creates a full deep copy of Spec/,+5d' ./pkg/apis/dashboard/v1beta1/dashboard_object_gen.go > ./pkg/apis/dashboard/v1beta1/dashboard_object_gen.go.tmp + @sed -e '/\/\/ DeepCopyInto deep copies Spec into another Spec object/,+3d' ./pkg/apis/dashboard/v1beta1/dashboard_object_gen.go.tmp > ./pkg/apis/dashboard/v1beta1/dashboard_object_gen.go.tmp2 + @rm ./pkg/apis/dashboard/v1beta1/dashboard_object_gen.go.tmp + @mv ./pkg/apis/dashboard/v1beta1/dashboard_object_gen.go.tmp2 ./pkg/apis/dashboard/v1beta1/dashboard_object_gen.go # Copy dashboard/v2alpha1 spec so we can use it for schema validation @echo "// This file is managed by grafana-app-sdk - DO NOT EDIT MANUALLY" > ./pkg/apis/dashboard/v2alpha1/dashboard_spec.cue diff --git a/apps/dashboard/kinds/dashboard.cue b/apps/dashboard/kinds/dashboard.cue index 5be84dcccf3..b8aaf17bc7c 100644 --- a/apps/dashboard/kinds/dashboard.cue +++ b/apps/dashboard/kinds/dashboard.cue @@ -2,7 +2,7 @@ package kinds import ( v0 "github.com/grafana/grafana/sdkkinds/dashboard/v0alpha1" - v1 "github.com/grafana/grafana/sdkkinds/dashboard/v1alpha1" + v1 "github.com/grafana/grafana/sdkkinds/dashboard/v1beta1" v2 "github.com/grafana/grafana/sdkkinds/dashboard/v2alpha1" ) @@ -31,7 +31,7 @@ ConversionStatus: { dashboard: { kind: "Dashboard" pluralName: "Dashboards" - current: "v0alpha1" + current: "v1beta1" codegen: { ts: { @@ -55,7 +55,7 @@ dashboard: { status: DashboardStatus } } - "v1alpha1": { + "v1beta1": { schema: { spec: v1.DashboardSpec status: DashboardStatus diff --git a/apps/dashboard/kinds/v1alpha1/dashboard_spec.cue b/apps/dashboard/kinds/v1beta1/dashboard_spec.cue similarity index 90% rename from apps/dashboard/kinds/v1alpha1/dashboard_spec.cue rename to apps/dashboard/kinds/v1beta1/dashboard_spec.cue index 709fb40fd02..61571a00a64 100644 --- a/apps/dashboard/kinds/v1alpha1/dashboard_spec.cue +++ b/apps/dashboard/kinds/v1beta1/dashboard_spec.cue @@ -1,4 +1,4 @@ -package v1alpha1 +package v1beta1 // TODO: this outputs nothing. // For now, we use unstructured for the spec, diff --git a/apps/dashboard/pkg/apis/dashboard/v1alpha1/zz_generated.conversion.go b/apps/dashboard/pkg/apis/dashboard/v1alpha1/zz_generated.conversion.go deleted file mode 100644 index 85780124520..00000000000 --- a/apps/dashboard/pkg/apis/dashboard/v1alpha1/zz_generated.conversion.go +++ /dev/null @@ -1,175 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -// SPDX-License-Identifier: AGPL-3.0-only - -// Code generated by conversion-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - url "net/url" - unsafe "unsafe" - - dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard" - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" -) - -func init() { - localSchemeBuilder.Register(RegisterConversions) -} - -// RegisterConversions adds conversion functions to the given scheme. -// Public to allow building arbitrary schemes. -func RegisterConversions(s *runtime.Scheme) error { - if err := s.AddGeneratedConversionFunc((*AnnotationActions)(nil), (*dashboard.AnnotationActions)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_AnnotationActions_To_dashboard_AnnotationActions(a.(*AnnotationActions), b.(*dashboard.AnnotationActions), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*dashboard.AnnotationActions)(nil), (*AnnotationActions)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_dashboard_AnnotationActions_To_v1alpha1_AnnotationActions(a.(*dashboard.AnnotationActions), b.(*AnnotationActions), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*AnnotationPermission)(nil), (*dashboard.AnnotationPermission)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_AnnotationPermission_To_dashboard_AnnotationPermission(a.(*AnnotationPermission), b.(*dashboard.AnnotationPermission), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*dashboard.AnnotationPermission)(nil), (*AnnotationPermission)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_dashboard_AnnotationPermission_To_v1alpha1_AnnotationPermission(a.(*dashboard.AnnotationPermission), b.(*AnnotationPermission), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*DashboardAccess)(nil), (*dashboard.DashboardAccess)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_DashboardAccess_To_dashboard_DashboardAccess(a.(*DashboardAccess), b.(*dashboard.DashboardAccess), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*dashboard.DashboardAccess)(nil), (*DashboardAccess)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_dashboard_DashboardAccess_To_v1alpha1_DashboardAccess(a.(*dashboard.DashboardAccess), b.(*DashboardAccess), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*url.Values)(nil), (*VersionsQueryOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_url_Values_To_v1alpha1_VersionsQueryOptions(a.(*url.Values), b.(*VersionsQueryOptions), scope) - }); err != nil { - return err - } - return nil -} - -func autoConvert_v1alpha1_AnnotationActions_To_dashboard_AnnotationActions(in *AnnotationActions, out *dashboard.AnnotationActions, s conversion.Scope) error { - out.CanAdd = in.CanAdd - out.CanEdit = in.CanEdit - out.CanDelete = in.CanDelete - return nil -} - -// Convert_v1alpha1_AnnotationActions_To_dashboard_AnnotationActions is an autogenerated conversion function. -func Convert_v1alpha1_AnnotationActions_To_dashboard_AnnotationActions(in *AnnotationActions, out *dashboard.AnnotationActions, s conversion.Scope) error { - return autoConvert_v1alpha1_AnnotationActions_To_dashboard_AnnotationActions(in, out, s) -} - -func autoConvert_dashboard_AnnotationActions_To_v1alpha1_AnnotationActions(in *dashboard.AnnotationActions, out *AnnotationActions, s conversion.Scope) error { - out.CanAdd = in.CanAdd - out.CanEdit = in.CanEdit - out.CanDelete = in.CanDelete - return nil -} - -// Convert_dashboard_AnnotationActions_To_v1alpha1_AnnotationActions is an autogenerated conversion function. -func Convert_dashboard_AnnotationActions_To_v1alpha1_AnnotationActions(in *dashboard.AnnotationActions, out *AnnotationActions, s conversion.Scope) error { - return autoConvert_dashboard_AnnotationActions_To_v1alpha1_AnnotationActions(in, out, s) -} - -func autoConvert_v1alpha1_AnnotationPermission_To_dashboard_AnnotationPermission(in *AnnotationPermission, out *dashboard.AnnotationPermission, s conversion.Scope) error { - if err := Convert_v1alpha1_AnnotationActions_To_dashboard_AnnotationActions(&in.Dashboard, &out.Dashboard, s); err != nil { - return err - } - if err := Convert_v1alpha1_AnnotationActions_To_dashboard_AnnotationActions(&in.Organization, &out.Organization, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha1_AnnotationPermission_To_dashboard_AnnotationPermission is an autogenerated conversion function. -func Convert_v1alpha1_AnnotationPermission_To_dashboard_AnnotationPermission(in *AnnotationPermission, out *dashboard.AnnotationPermission, s conversion.Scope) error { - return autoConvert_v1alpha1_AnnotationPermission_To_dashboard_AnnotationPermission(in, out, s) -} - -func autoConvert_dashboard_AnnotationPermission_To_v1alpha1_AnnotationPermission(in *dashboard.AnnotationPermission, out *AnnotationPermission, s conversion.Scope) error { - if err := Convert_dashboard_AnnotationActions_To_v1alpha1_AnnotationActions(&in.Dashboard, &out.Dashboard, s); err != nil { - return err - } - if err := Convert_dashboard_AnnotationActions_To_v1alpha1_AnnotationActions(&in.Organization, &out.Organization, s); err != nil { - return err - } - return nil -} - -// Convert_dashboard_AnnotationPermission_To_v1alpha1_AnnotationPermission is an autogenerated conversion function. -func Convert_dashboard_AnnotationPermission_To_v1alpha1_AnnotationPermission(in *dashboard.AnnotationPermission, out *AnnotationPermission, s conversion.Scope) error { - return autoConvert_dashboard_AnnotationPermission_To_v1alpha1_AnnotationPermission(in, out, s) -} - -func autoConvert_v1alpha1_DashboardAccess_To_dashboard_DashboardAccess(in *DashboardAccess, out *dashboard.DashboardAccess, s conversion.Scope) error { - out.Slug = in.Slug - out.Url = in.Url - out.CanSave = in.CanSave - out.CanEdit = in.CanEdit - out.CanAdmin = in.CanAdmin - out.CanStar = in.CanStar - out.CanDelete = in.CanDelete - out.AnnotationsPermissions = (*dashboard.AnnotationPermission)(unsafe.Pointer(in.AnnotationsPermissions)) - return nil -} - -// Convert_v1alpha1_DashboardAccess_To_dashboard_DashboardAccess is an autogenerated conversion function. -func Convert_v1alpha1_DashboardAccess_To_dashboard_DashboardAccess(in *DashboardAccess, out *dashboard.DashboardAccess, s conversion.Scope) error { - return autoConvert_v1alpha1_DashboardAccess_To_dashboard_DashboardAccess(in, out, s) -} - -func autoConvert_dashboard_DashboardAccess_To_v1alpha1_DashboardAccess(in *dashboard.DashboardAccess, out *DashboardAccess, s conversion.Scope) error { - out.Slug = in.Slug - out.Url = in.Url - out.CanSave = in.CanSave - out.CanEdit = in.CanEdit - out.CanAdmin = in.CanAdmin - out.CanStar = in.CanStar - out.CanDelete = in.CanDelete - out.AnnotationsPermissions = (*AnnotationPermission)(unsafe.Pointer(in.AnnotationsPermissions)) - return nil -} - -// Convert_dashboard_DashboardAccess_To_v1alpha1_DashboardAccess is an autogenerated conversion function. -func Convert_dashboard_DashboardAccess_To_v1alpha1_DashboardAccess(in *dashboard.DashboardAccess, out *DashboardAccess, s conversion.Scope) error { - return autoConvert_dashboard_DashboardAccess_To_v1alpha1_DashboardAccess(in, out, s) -} - -func autoConvert_url_Values_To_v1alpha1_VersionsQueryOptions(in *url.Values, out *VersionsQueryOptions, s conversion.Scope) error { - // WARNING: Field TypeMeta does not have json tag, skipping. - - if values, ok := map[string][]string(*in)["path"]; ok && len(values) > 0 { - if err := runtime.Convert_Slice_string_To_string(&values, &out.Path, s); err != nil { - return err - } - } else { - out.Path = "" - } - if values, ok := map[string][]string(*in)["version"]; ok && len(values) > 0 { - if err := runtime.Convert_Slice_string_To_int64(&values, &out.Version, s); err != nil { - return err - } - } else { - out.Version = 0 - } - return nil -} - -// Convert_url_Values_To_v1alpha1_VersionsQueryOptions is an autogenerated conversion function. -func Convert_url_Values_To_v1alpha1_VersionsQueryOptions(in *url.Values, out *VersionsQueryOptions, s conversion.Scope) error { - return autoConvert_url_Values_To_v1alpha1_VersionsQueryOptions(in, out, s) -} diff --git a/apps/dashboard/pkg/apis/dashboard/v1alpha1/constants.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/constants.go similarity index 91% rename from apps/dashboard/pkg/apis/dashboard/v1alpha1/constants.go rename to apps/dashboard/pkg/apis/dashboard/v1beta1/constants.go index b3732986f0b..d47d860d95f 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1alpha1/constants.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/constants.go @@ -1,4 +1,4 @@ -package v1alpha1 +package v1beta1 import "k8s.io/apimachinery/pkg/runtime/schema" @@ -6,7 +6,7 @@ const ( // Group is the API group used by all kinds in this package Group = "dashboard.grafana.app" // Version is the API version used by all kinds in this package - Version = "v1alpha1" + Version = "v1beta1" ) var ( diff --git a/apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_codec_gen.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_codec_gen.go similarity index 97% rename from apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_codec_gen.go rename to apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_codec_gen.go index 24ee7ecd7bd..c5df405aed8 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_codec_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_codec_gen.go @@ -2,7 +2,7 @@ // Code generated by grafana-app-sdk. DO NOT EDIT. // -package v1alpha1 +package v1beta1 import ( "encoding/json" diff --git a/apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_kind.cue b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue similarity index 100% rename from apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_kind.cue rename to apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue diff --git a/apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_metadata_gen.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_metadata_gen.go similarity index 98% rename from apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_metadata_gen.go rename to apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_metadata_gen.go index 16ead71d265..dfad3b5cfe7 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_metadata_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_metadata_gen.go @@ -1,6 +1,6 @@ // Code generated - EDITING IS FUTILE. DO NOT EDIT. -package v1alpha1 +package v1beta1 import ( time "time" diff --git a/apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_object_gen.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_object_gen.go similarity index 99% rename from apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_object_gen.go rename to apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_object_gen.go index 80af0b99526..be021b5f003 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_object_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_object_gen.go @@ -2,7 +2,7 @@ // Code generated by grafana-app-sdk. DO NOT EDIT. // -package v1alpha1 +package v1beta1 import ( "fmt" diff --git a/apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_schema_gen.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_schema_gen.go similarity index 89% rename from apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_schema_gen.go rename to apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_schema_gen.go index 1c09a83e2bd..e944e0afc33 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_schema_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_schema_gen.go @@ -2,7 +2,7 @@ // Code generated by grafana-app-sdk. DO NOT EDIT. // -package v1alpha1 +package v1beta1 import ( "github.com/grafana/grafana-app-sdk/resource" @@ -10,7 +10,7 @@ import ( // schema is unexported to prevent accidental overwrites var ( - schemaDashboard = resource.NewSimpleSchema("dashboard.grafana.app", "v1alpha1", &Dashboard{}, &DashboardList{}, resource.WithKind("Dashboard"), + schemaDashboard = resource.NewSimpleSchema("dashboard.grafana.app", "v1beta1", &Dashboard{}, &DashboardList{}, resource.WithKind("Dashboard"), resource.WithPlural("dashboards"), resource.WithScope(resource.NamespacedScope)) kindDashboard = resource.Kind{ Schema: schemaDashboard, diff --git a/apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_spec.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_spec.go similarity index 93% rename from apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_spec.go rename to apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_spec.go index 0b341e6e55e..154208f46a7 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_spec.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_spec.go @@ -1,4 +1,4 @@ -package v1alpha1 +package v1beta1 import common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" diff --git a/apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_spec_gen.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_spec_gen.go similarity index 75% rename from apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_spec_gen.go rename to apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_spec_gen.go index 4f48fee9aae..1d1cd1f7d24 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_spec_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_spec_gen.go @@ -1,3 +1,3 @@ // Code generated - EDITING IS FUTILE. DO NOT EDIT. -package v1alpha1 +package v1beta1 diff --git a/apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_status_gen.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_status_gen.go similarity index 98% rename from apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_status_gen.go rename to apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_status_gen.go index 556d5ddba53..5213df7919b 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1alpha1/dashboard_status_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_status_gen.go @@ -1,6 +1,6 @@ // Code generated - EDITING IS FUTILE. DO NOT EDIT. -package v1alpha1 +package v1beta1 // ConversionStatus is the status of the conversion of the dashboard. // +k8s:openapi-gen=true diff --git a/apps/dashboard/pkg/apis/dashboard/v1alpha1/doc.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/doc.go similarity index 80% rename from apps/dashboard/pkg/apis/dashboard/v1alpha1/doc.go rename to apps/dashboard/pkg/apis/dashboard/v1beta1/doc.go index 66bf673964c..4ffc3214e97 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1alpha1/doc.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/doc.go @@ -7,4 +7,4 @@ // because grafana-app-sdk already provides deepcopy functions. // Kinds which are not generated by the SDK are explicitly opted in to deepcopy generation. -package v1alpha1 // import "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" +package v1beta1 // import "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" diff --git a/apps/dashboard/pkg/apis/dashboard/v1alpha1/register.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/register.go similarity index 98% rename from apps/dashboard/pkg/apis/dashboard/v1alpha1/register.go rename to apps/dashboard/pkg/apis/dashboard/v1beta1/register.go index 4be26c5e54c..2147d30f876 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1alpha1/register.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/register.go @@ -1,4 +1,4 @@ -package v1alpha1 +package v1beta1 import ( "fmt" @@ -13,7 +13,7 @@ import ( const ( GROUP = "dashboard.grafana.app" - VERSION = "v1alpha1" + VERSION = "v1beta1" APIVERSION = GROUP + "/" + VERSION // Resource constants diff --git a/apps/dashboard/pkg/apis/dashboard/v1alpha1/types.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/types.go similarity index 99% rename from apps/dashboard/pkg/apis/dashboard/v1alpha1/types.go rename to apps/dashboard/pkg/apis/dashboard/v1beta1/types.go index 43844655505..a3f93c3a29c 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1alpha1/types.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/types.go @@ -1,4 +1,4 @@ -package v1alpha1 +package v1beta1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/apps/dashboard/pkg/apis/dashboard/v1alpha1/validation.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/validation.go similarity index 99% rename from apps/dashboard/pkg/apis/dashboard/v1alpha1/validation.go rename to apps/dashboard/pkg/apis/dashboard/v1beta1/validation.go index 37117b10108..7020aa46b90 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1alpha1/validation.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/validation.go @@ -1,4 +1,4 @@ -package v1alpha1 +package v1beta1 import ( _ "embed" @@ -13,6 +13,7 @@ import ( "cuelang.org/go/cue/cuecontext" "cuelang.org/go/cue/errors" cuejson "cuelang.org/go/encoding/json" + "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" ) diff --git a/apps/dashboard/pkg/apis/dashboard/v1beta1/zz_generated.conversion.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/zz_generated.conversion.go new file mode 100644 index 00000000000..e3f1f46785b --- /dev/null +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/zz_generated.conversion.go @@ -0,0 +1,175 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by conversion-gen. DO NOT EDIT. + +package v1beta1 + +import ( + url "net/url" + unsafe "unsafe" + + dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard" + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +func init() { + localSchemeBuilder.Register(RegisterConversions) +} + +// RegisterConversions adds conversion functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterConversions(s *runtime.Scheme) error { + if err := s.AddGeneratedConversionFunc((*AnnotationActions)(nil), (*dashboard.AnnotationActions)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_AnnotationActions_To_dashboard_AnnotationActions(a.(*AnnotationActions), b.(*dashboard.AnnotationActions), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*dashboard.AnnotationActions)(nil), (*AnnotationActions)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_dashboard_AnnotationActions_To_v1beta1_AnnotationActions(a.(*dashboard.AnnotationActions), b.(*AnnotationActions), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*AnnotationPermission)(nil), (*dashboard.AnnotationPermission)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_AnnotationPermission_To_dashboard_AnnotationPermission(a.(*AnnotationPermission), b.(*dashboard.AnnotationPermission), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*dashboard.AnnotationPermission)(nil), (*AnnotationPermission)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_dashboard_AnnotationPermission_To_v1beta1_AnnotationPermission(a.(*dashboard.AnnotationPermission), b.(*AnnotationPermission), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*DashboardAccess)(nil), (*dashboard.DashboardAccess)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_DashboardAccess_To_dashboard_DashboardAccess(a.(*DashboardAccess), b.(*dashboard.DashboardAccess), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*dashboard.DashboardAccess)(nil), (*DashboardAccess)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_dashboard_DashboardAccess_To_v1beta1_DashboardAccess(a.(*dashboard.DashboardAccess), b.(*DashboardAccess), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*url.Values)(nil), (*VersionsQueryOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_url_Values_To_v1beta1_VersionsQueryOptions(a.(*url.Values), b.(*VersionsQueryOptions), scope) + }); err != nil { + return err + } + return nil +} + +func autoConvert_v1beta1_AnnotationActions_To_dashboard_AnnotationActions(in *AnnotationActions, out *dashboard.AnnotationActions, s conversion.Scope) error { + out.CanAdd = in.CanAdd + out.CanEdit = in.CanEdit + out.CanDelete = in.CanDelete + return nil +} + +// Convert_v1beta1_AnnotationActions_To_dashboard_AnnotationActions is an autogenerated conversion function. +func Convert_v1beta1_AnnotationActions_To_dashboard_AnnotationActions(in *AnnotationActions, out *dashboard.AnnotationActions, s conversion.Scope) error { + return autoConvert_v1beta1_AnnotationActions_To_dashboard_AnnotationActions(in, out, s) +} + +func autoConvert_dashboard_AnnotationActions_To_v1beta1_AnnotationActions(in *dashboard.AnnotationActions, out *AnnotationActions, s conversion.Scope) error { + out.CanAdd = in.CanAdd + out.CanEdit = in.CanEdit + out.CanDelete = in.CanDelete + return nil +} + +// Convert_dashboard_AnnotationActions_To_v1beta1_AnnotationActions is an autogenerated conversion function. +func Convert_dashboard_AnnotationActions_To_v1beta1_AnnotationActions(in *dashboard.AnnotationActions, out *AnnotationActions, s conversion.Scope) error { + return autoConvert_dashboard_AnnotationActions_To_v1beta1_AnnotationActions(in, out, s) +} + +func autoConvert_v1beta1_AnnotationPermission_To_dashboard_AnnotationPermission(in *AnnotationPermission, out *dashboard.AnnotationPermission, s conversion.Scope) error { + if err := Convert_v1beta1_AnnotationActions_To_dashboard_AnnotationActions(&in.Dashboard, &out.Dashboard, s); err != nil { + return err + } + if err := Convert_v1beta1_AnnotationActions_To_dashboard_AnnotationActions(&in.Organization, &out.Organization, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta1_AnnotationPermission_To_dashboard_AnnotationPermission is an autogenerated conversion function. +func Convert_v1beta1_AnnotationPermission_To_dashboard_AnnotationPermission(in *AnnotationPermission, out *dashboard.AnnotationPermission, s conversion.Scope) error { + return autoConvert_v1beta1_AnnotationPermission_To_dashboard_AnnotationPermission(in, out, s) +} + +func autoConvert_dashboard_AnnotationPermission_To_v1beta1_AnnotationPermission(in *dashboard.AnnotationPermission, out *AnnotationPermission, s conversion.Scope) error { + if err := Convert_dashboard_AnnotationActions_To_v1beta1_AnnotationActions(&in.Dashboard, &out.Dashboard, s); err != nil { + return err + } + if err := Convert_dashboard_AnnotationActions_To_v1beta1_AnnotationActions(&in.Organization, &out.Organization, s); err != nil { + return err + } + return nil +} + +// Convert_dashboard_AnnotationPermission_To_v1beta1_AnnotationPermission is an autogenerated conversion function. +func Convert_dashboard_AnnotationPermission_To_v1beta1_AnnotationPermission(in *dashboard.AnnotationPermission, out *AnnotationPermission, s conversion.Scope) error { + return autoConvert_dashboard_AnnotationPermission_To_v1beta1_AnnotationPermission(in, out, s) +} + +func autoConvert_v1beta1_DashboardAccess_To_dashboard_DashboardAccess(in *DashboardAccess, out *dashboard.DashboardAccess, s conversion.Scope) error { + out.Slug = in.Slug + out.Url = in.Url + out.CanSave = in.CanSave + out.CanEdit = in.CanEdit + out.CanAdmin = in.CanAdmin + out.CanStar = in.CanStar + out.CanDelete = in.CanDelete + out.AnnotationsPermissions = (*dashboard.AnnotationPermission)(unsafe.Pointer(in.AnnotationsPermissions)) + return nil +} + +// Convert_v1beta1_DashboardAccess_To_dashboard_DashboardAccess is an autogenerated conversion function. +func Convert_v1beta1_DashboardAccess_To_dashboard_DashboardAccess(in *DashboardAccess, out *dashboard.DashboardAccess, s conversion.Scope) error { + return autoConvert_v1beta1_DashboardAccess_To_dashboard_DashboardAccess(in, out, s) +} + +func autoConvert_dashboard_DashboardAccess_To_v1beta1_DashboardAccess(in *dashboard.DashboardAccess, out *DashboardAccess, s conversion.Scope) error { + out.Slug = in.Slug + out.Url = in.Url + out.CanSave = in.CanSave + out.CanEdit = in.CanEdit + out.CanAdmin = in.CanAdmin + out.CanStar = in.CanStar + out.CanDelete = in.CanDelete + out.AnnotationsPermissions = (*AnnotationPermission)(unsafe.Pointer(in.AnnotationsPermissions)) + return nil +} + +// Convert_dashboard_DashboardAccess_To_v1beta1_DashboardAccess is an autogenerated conversion function. +func Convert_dashboard_DashboardAccess_To_v1beta1_DashboardAccess(in *dashboard.DashboardAccess, out *DashboardAccess, s conversion.Scope) error { + return autoConvert_dashboard_DashboardAccess_To_v1beta1_DashboardAccess(in, out, s) +} + +func autoConvert_url_Values_To_v1beta1_VersionsQueryOptions(in *url.Values, out *VersionsQueryOptions, s conversion.Scope) error { + // WARNING: Field TypeMeta does not have json tag, skipping. + + if values, ok := map[string][]string(*in)["path"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_string(&values, &out.Path, s); err != nil { + return err + } + } else { + out.Path = "" + } + if values, ok := map[string][]string(*in)["version"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_int64(&values, &out.Version, s); err != nil { + return err + } + } else { + out.Version = 0 + } + return nil +} + +// Convert_url_Values_To_v1beta1_VersionsQueryOptions is an autogenerated conversion function. +func Convert_url_Values_To_v1beta1_VersionsQueryOptions(in *url.Values, out *VersionsQueryOptions, s conversion.Scope) error { + return autoConvert_url_Values_To_v1beta1_VersionsQueryOptions(in, out, s) +} diff --git a/apps/dashboard/pkg/apis/dashboard/v1alpha1/zz_generated.deepcopy.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/zz_generated.deepcopy.go similarity index 99% rename from apps/dashboard/pkg/apis/dashboard/v1alpha1/zz_generated.deepcopy.go rename to apps/dashboard/pkg/apis/dashboard/v1beta1/zz_generated.deepcopy.go index 8e38161cc58..d60a6d25f98 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1alpha1/zz_generated.deepcopy.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/zz_generated.deepcopy.go @@ -5,7 +5,7 @@ // Code generated by deepcopy-gen. DO NOT EDIT. -package v1alpha1 +package v1beta1 import ( v0alpha1 "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" diff --git a/apps/dashboard/pkg/apis/dashboard/v1alpha1/zz_generated.defaults.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/zz_generated.defaults.go similarity index 96% rename from apps/dashboard/pkg/apis/dashboard/v1alpha1/zz_generated.defaults.go rename to apps/dashboard/pkg/apis/dashboard/v1beta1/zz_generated.defaults.go index 0f877b0ee12..49d2a788aab 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1alpha1/zz_generated.defaults.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/zz_generated.defaults.go @@ -5,7 +5,7 @@ // Code generated by defaulter-gen. DO NOT EDIT. -package v1alpha1 +package v1beta1 import ( runtime "k8s.io/apimachinery/pkg/runtime" diff --git a/apps/dashboard/pkg/apis/dashboard/v1alpha1/zz_generated.openapi.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/zz_generated.openapi.go similarity index 84% rename from apps/dashboard/pkg/apis/dashboard/v1alpha1/zz_generated.openapi.go rename to apps/dashboard/pkg/apis/dashboard/v1beta1/zz_generated.openapi.go index 9e3bb475d68..3b2175bd003 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1alpha1/zz_generated.openapi.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/zz_generated.openapi.go @@ -5,7 +5,7 @@ // Code generated by openapi-gen. DO NOT EDIT. -package v1alpha1 +package v1beta1 import ( v0alpha1 "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" @@ -15,28 +15,28 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.AnnotationActions": schema_pkg_apis_dashboard_v1alpha1_AnnotationActions(ref), - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.AnnotationPermission": schema_pkg_apis_dashboard_v1alpha1_AnnotationPermission(ref), - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.Dashboard": schema_pkg_apis_dashboard_v1alpha1_Dashboard(ref), - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.DashboardAccess": schema_pkg_apis_dashboard_v1alpha1_DashboardAccess(ref), - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.DashboardConversionStatus": schema_pkg_apis_dashboard_v1alpha1_DashboardConversionStatus(ref), - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.DashboardJSONCodec": schema_pkg_apis_dashboard_v1alpha1_DashboardJSONCodec(ref), - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.DashboardList": schema_pkg_apis_dashboard_v1alpha1_DashboardList(ref), - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.DashboardMetadata": schema_pkg_apis_dashboard_v1alpha1_DashboardMetadata(ref), - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.DashboardStatus": schema_pkg_apis_dashboard_v1alpha1_DashboardStatus(ref), - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.DashboardVersionInfo": schema_pkg_apis_dashboard_v1alpha1_DashboardVersionInfo(ref), - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.DashboardVersionList": schema_pkg_apis_dashboard_v1alpha1_DashboardVersionList(ref), - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.DashboardWithAccessInfo": schema_pkg_apis_dashboard_v1alpha1_DashboardWithAccessInfo(ref), - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.LibraryPanel": schema_pkg_apis_dashboard_v1alpha1_LibraryPanel(ref), - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.LibraryPanelList": schema_pkg_apis_dashboard_v1alpha1_LibraryPanelList(ref), - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.LibraryPanelSpec": schema_pkg_apis_dashboard_v1alpha1_LibraryPanelSpec(ref), - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.LibraryPanelStatus": schema_pkg_apis_dashboard_v1alpha1_LibraryPanelStatus(ref), - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.VersionsQueryOptions": schema_pkg_apis_dashboard_v1alpha1_VersionsQueryOptions(ref), - "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured": v0alpha1.Unstructured{}.OpenAPIDefinition(), + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.AnnotationActions": schema_pkg_apis_dashboard_v1beta1_AnnotationActions(ref), + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.AnnotationPermission": schema_pkg_apis_dashboard_v1beta1_AnnotationPermission(ref), + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.Dashboard": schema_pkg_apis_dashboard_v1beta1_Dashboard(ref), + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.DashboardAccess": schema_pkg_apis_dashboard_v1beta1_DashboardAccess(ref), + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.DashboardConversionStatus": schema_pkg_apis_dashboard_v1beta1_DashboardConversionStatus(ref), + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.DashboardJSONCodec": schema_pkg_apis_dashboard_v1beta1_DashboardJSONCodec(ref), + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.DashboardList": schema_pkg_apis_dashboard_v1beta1_DashboardList(ref), + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.DashboardMetadata": schema_pkg_apis_dashboard_v1beta1_DashboardMetadata(ref), + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.DashboardStatus": schema_pkg_apis_dashboard_v1beta1_DashboardStatus(ref), + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.DashboardVersionInfo": schema_pkg_apis_dashboard_v1beta1_DashboardVersionInfo(ref), + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.DashboardVersionList": schema_pkg_apis_dashboard_v1beta1_DashboardVersionList(ref), + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.DashboardWithAccessInfo": schema_pkg_apis_dashboard_v1beta1_DashboardWithAccessInfo(ref), + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.LibraryPanel": schema_pkg_apis_dashboard_v1beta1_LibraryPanel(ref), + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.LibraryPanelList": schema_pkg_apis_dashboard_v1beta1_LibraryPanelList(ref), + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.LibraryPanelSpec": schema_pkg_apis_dashboard_v1beta1_LibraryPanelSpec(ref), + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.LibraryPanelStatus": schema_pkg_apis_dashboard_v1beta1_LibraryPanelStatus(ref), + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.VersionsQueryOptions": schema_pkg_apis_dashboard_v1beta1_VersionsQueryOptions(ref), + "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured": v0alpha1.Unstructured{}.OpenAPIDefinition(), } } -func schema_pkg_apis_dashboard_v1alpha1_AnnotationActions(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_dashboard_v1beta1_AnnotationActions(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -70,7 +70,7 @@ func schema_pkg_apis_dashboard_v1alpha1_AnnotationActions(ref common.ReferenceCa } } -func schema_pkg_apis_dashboard_v1alpha1_AnnotationPermission(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_dashboard_v1beta1_AnnotationPermission(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -79,13 +79,13 @@ func schema_pkg_apis_dashboard_v1alpha1_AnnotationPermission(ref common.Referenc "dashboard": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.AnnotationActions"), + Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.AnnotationActions"), }, }, "organization": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.AnnotationActions"), + Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.AnnotationActions"), }, }, }, @@ -93,11 +93,11 @@ func schema_pkg_apis_dashboard_v1alpha1_AnnotationPermission(ref common.Referenc }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.AnnotationActions"}, + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.AnnotationActions"}, } } -func schema_pkg_apis_dashboard_v1alpha1_Dashboard(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_dashboard_v1beta1_Dashboard(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -132,7 +132,7 @@ func schema_pkg_apis_dashboard_v1alpha1_Dashboard(ref common.ReferenceCallback) "status": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.DashboardStatus"), + Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.DashboardStatus"), }, }, }, @@ -140,11 +140,11 @@ func schema_pkg_apis_dashboard_v1alpha1_Dashboard(ref common.ReferenceCallback) }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.DashboardStatus", "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.DashboardStatus", "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } -func schema_pkg_apis_dashboard_v1alpha1_DashboardAccess(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_dashboard_v1beta1_DashboardAccess(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -202,7 +202,7 @@ func schema_pkg_apis_dashboard_v1alpha1_DashboardAccess(ref common.ReferenceCall }, "annotationsPermissions": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.AnnotationPermission"), + Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.AnnotationPermission"), }, }, }, @@ -210,11 +210,11 @@ func schema_pkg_apis_dashboard_v1alpha1_DashboardAccess(ref common.ReferenceCall }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.AnnotationPermission"}, + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.AnnotationPermission"}, } } -func schema_pkg_apis_dashboard_v1alpha1_DashboardConversionStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_dashboard_v1beta1_DashboardConversionStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -252,7 +252,7 @@ func schema_pkg_apis_dashboard_v1alpha1_DashboardConversionStatus(ref common.Ref } } -func schema_pkg_apis_dashboard_v1alpha1_DashboardJSONCodec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_dashboard_v1beta1_DashboardJSONCodec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -263,7 +263,7 @@ func schema_pkg_apis_dashboard_v1alpha1_DashboardJSONCodec(ref common.ReferenceC } } -func schema_pkg_apis_dashboard_v1alpha1_DashboardList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_dashboard_v1beta1_DashboardList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -296,7 +296,7 @@ func schema_pkg_apis_dashboard_v1alpha1_DashboardList(ref common.ReferenceCallba Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.Dashboard"), + Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.Dashboard"), }, }, }, @@ -307,11 +307,11 @@ func schema_pkg_apis_dashboard_v1alpha1_DashboardList(ref common.ReferenceCallba }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.Dashboard", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.Dashboard", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } -func schema_pkg_apis_dashboard_v1alpha1_DashboardMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_dashboard_v1beta1_DashboardMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -407,7 +407,7 @@ func schema_pkg_apis_dashboard_v1alpha1_DashboardMetadata(ref common.ReferenceCa } } -func schema_pkg_apis_dashboard_v1alpha1_DashboardStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_dashboard_v1beta1_DashboardStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -416,18 +416,18 @@ func schema_pkg_apis_dashboard_v1alpha1_DashboardStatus(ref common.ReferenceCall "conversion": { SchemaProps: spec.SchemaProps{ Description: "Optional conversion status.", - Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.DashboardConversionStatus"), + Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.DashboardConversionStatus"), }, }, }, }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.DashboardConversionStatus"}, + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.DashboardConversionStatus"}, } } -func schema_pkg_apis_dashboard_v1alpha1_DashboardVersionInfo(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_dashboard_v1beta1_DashboardVersionInfo(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -477,7 +477,7 @@ func schema_pkg_apis_dashboard_v1alpha1_DashboardVersionInfo(ref common.Referenc } } -func schema_pkg_apis_dashboard_v1alpha1_DashboardVersionList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_dashboard_v1beta1_DashboardVersionList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -510,7 +510,7 @@ func schema_pkg_apis_dashboard_v1alpha1_DashboardVersionList(ref common.Referenc Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.DashboardVersionInfo"), + Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.DashboardVersionInfo"), }, }, }, @@ -520,11 +520,11 @@ func schema_pkg_apis_dashboard_v1alpha1_DashboardVersionList(ref common.Referenc }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.DashboardVersionInfo", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.DashboardVersionInfo", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } -func schema_pkg_apis_dashboard_v1alpha1_DashboardWithAccessInfo(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_dashboard_v1beta1_DashboardWithAccessInfo(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -560,13 +560,13 @@ func schema_pkg_apis_dashboard_v1alpha1_DashboardWithAccessInfo(ref common.Refer "status": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.DashboardStatus"), + Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.DashboardStatus"), }, }, "access": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.DashboardAccess"), + Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.DashboardAccess"), }, }, }, @@ -574,11 +574,11 @@ func schema_pkg_apis_dashboard_v1alpha1_DashboardWithAccessInfo(ref common.Refer }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.DashboardAccess", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.DashboardStatus", "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.DashboardAccess", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.DashboardStatus", "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } -func schema_pkg_apis_dashboard_v1alpha1_LibraryPanel(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_dashboard_v1beta1_LibraryPanel(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -609,13 +609,13 @@ func schema_pkg_apis_dashboard_v1alpha1_LibraryPanel(ref common.ReferenceCallbac SchemaProps: spec.SchemaProps{ Description: "Panel properties", Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.LibraryPanelSpec"), + Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.LibraryPanelSpec"), }, }, "status": { SchemaProps: spec.SchemaProps{ Description: "Status will show errors", - Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.LibraryPanelStatus"), + Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.LibraryPanelStatus"), }, }, }, @@ -623,11 +623,11 @@ func schema_pkg_apis_dashboard_v1alpha1_LibraryPanel(ref common.ReferenceCallbac }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.LibraryPanelSpec", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.LibraryPanelStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.LibraryPanelSpec", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.LibraryPanelStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } -func schema_pkg_apis_dashboard_v1alpha1_LibraryPanelList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_dashboard_v1beta1_LibraryPanelList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -660,7 +660,7 @@ func schema_pkg_apis_dashboard_v1alpha1_LibraryPanelList(ref common.ReferenceCal Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.LibraryPanel"), + Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.LibraryPanel"), }, }, }, @@ -670,11 +670,11 @@ func schema_pkg_apis_dashboard_v1alpha1_LibraryPanelList(ref common.ReferenceCal }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1.LibraryPanel", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1.LibraryPanel", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } -func schema_pkg_apis_dashboard_v1alpha1_LibraryPanelSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_dashboard_v1beta1_LibraryPanelSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -754,7 +754,7 @@ func schema_pkg_apis_dashboard_v1alpha1_LibraryPanelSpec(ref common.ReferenceCal } } -func schema_pkg_apis_dashboard_v1alpha1_LibraryPanelStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_dashboard_v1beta1_LibraryPanelStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -789,7 +789,7 @@ func schema_pkg_apis_dashboard_v1alpha1_LibraryPanelStatus(ref common.ReferenceC } } -func schema_pkg_apis_dashboard_v1alpha1_VersionsQueryOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_dashboard_v1beta1_VersionsQueryOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ diff --git a/apps/dashboard/pkg/apis/dashboard/v1alpha1/zz_generated.openapi_violation_exceptions.list b/apps/dashboard/pkg/apis/dashboard/v1beta1/zz_generated.openapi_violation_exceptions.list similarity index 62% rename from apps/dashboard/pkg/apis/dashboard/v1alpha1/zz_generated.openapi_violation_exceptions.list rename to apps/dashboard/pkg/apis/dashboard/v1beta1/zz_generated.openapi_violation_exceptions.list index 9a9b51f93c7..a4bab110b14 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1alpha1/zz_generated.openapi_violation_exceptions.list +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/zz_generated.openapi_violation_exceptions.list @@ -1,3 +1,3 @@ -API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1,DashboardMetadata,Finalizers -API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1,LibraryPanelStatus,Warnings +API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1,DashboardMetadata,Finalizers +API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1,LibraryPanelStatus,Warnings API rule violation: names_match,github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1,Unstructured,Object diff --git a/apps/dashboard/pkg/apis/dashboard_manifest.go b/apps/dashboard/pkg/apis/dashboard_manifest.go index 9980594ef44..cc5451a78e8 100644 --- a/apps/dashboard/pkg/apis/dashboard_manifest.go +++ b/apps/dashboard/pkg/apis/dashboard_manifest.go @@ -25,7 +25,7 @@ var appManifestData = app.ManifestData{ }, { - Name: "v1alpha1", + Name: "v1beta1", }, { diff --git a/apps/dashboard/pkg/migration/conversion/conversion.go b/apps/dashboard/pkg/migration/conversion/conversion.go index cb11e024f85..ff9f1c0365c 100644 --- a/apps/dashboard/pkg/migration/conversion/conversion.go +++ b/apps/dashboard/pkg/migration/conversion/conversion.go @@ -5,7 +5,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" dashv0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" - dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" dashv2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" "github.com/grafana/grafana/apps/dashboard/pkg/migration" "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" diff --git a/apps/dashboard/pkg/migration/conversion/conversion_test.go b/apps/dashboard/pkg/migration/conversion/conversion_test.go index 243745ce0ab..248fbe70eaa 100644 --- a/apps/dashboard/pkg/migration/conversion/conversion_test.go +++ b/apps/dashboard/pkg/migration/conversion/conversion_test.go @@ -10,7 +10,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" dashv0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" - dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" dashv2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/utils" diff --git a/apps/dashboard/pkg/migration/migrate.go b/apps/dashboard/pkg/migration/migrate.go index 51d61772704..6cf4f1b4cf0 100644 --- a/apps/dashboard/pkg/migration/migrate.go +++ b/apps/dashboard/pkg/migration/migrate.go @@ -2,9 +2,9 @@ package migration import "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" -func Migrate(dash map[string]interface{}, targetVersion int) error { +func Migrate(dash map[string]any, targetVersion int) error { if dash == nil { - dash = map[string]interface{}{} + dash = map[string]any{} } inputVersion := schemaversion.GetSchemaVersion(dash) dash["schemaVersion"] = inputVersion diff --git a/conf/provisioning/sample/dashboard-v1.json b/conf/provisioning/sample/dashboard-v1.json index 6f5c272da70..6e7affce4a5 100644 --- a/conf/provisioning/sample/dashboard-v1.json +++ b/conf/provisioning/sample/dashboard-v1.json @@ -1,6 +1,6 @@ { "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v1alpha1", + "apiVersion": "dashboard.grafana.app/v1beta1", "metadata": { "name": "sample-dash", "annotations": { diff --git a/conf/provisioning/sample/folder-A/folder-B/dashboard-nested.json b/conf/provisioning/sample/folder-A/folder-B/dashboard-nested.json index 62b4ae7e583..1d0862469f6 100644 --- a/conf/provisioning/sample/folder-A/folder-B/dashboard-nested.json +++ b/conf/provisioning/sample/folder-A/folder-B/dashboard-nested.json @@ -1,6 +1,6 @@ { "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v1alpha1", + "apiVersion": "dashboard.grafana.app/v1beta1", "metadata": { "name": "dashboard-nested", "annotations": { diff --git a/packages/grafana-schema/src/schema/dashboard/v1alpha1/dashboard_object_gen.ts b/packages/grafana-schema/src/schema/dashboard/v1beta1/dashboard_object_gen.ts similarity index 100% rename from packages/grafana-schema/src/schema/dashboard/v1alpha1/dashboard_object_gen.ts rename to packages/grafana-schema/src/schema/dashboard/v1beta1/dashboard_object_gen.ts diff --git a/packages/grafana-schema/src/schema/dashboard/v1alpha1/types.metadata.gen.ts b/packages/grafana-schema/src/schema/dashboard/v1beta1/types.metadata.gen.ts similarity index 100% rename from packages/grafana-schema/src/schema/dashboard/v1alpha1/types.metadata.gen.ts rename to packages/grafana-schema/src/schema/dashboard/v1beta1/types.metadata.gen.ts diff --git a/packages/grafana-schema/src/schema/dashboard/v1alpha1/types.spec.gen.ts b/packages/grafana-schema/src/schema/dashboard/v1beta1/types.spec.gen.ts similarity index 100% rename from packages/grafana-schema/src/schema/dashboard/v1alpha1/types.spec.gen.ts rename to packages/grafana-schema/src/schema/dashboard/v1beta1/types.spec.gen.ts diff --git a/packages/grafana-schema/src/schema/dashboard/v1alpha1/types.status.gen.ts b/packages/grafana-schema/src/schema/dashboard/v1beta1/types.status.gen.ts similarity index 100% rename from packages/grafana-schema/src/schema/dashboard/v1alpha1/types.status.gen.ts rename to packages/grafana-schema/src/schema/dashboard/v1beta1/types.status.gen.ts diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index f4658d22305..b6a921d07c1 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -12,8 +12,10 @@ import ( "strconv" "strings" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + claims "github.com/grafana/authlib/types" - dashboardsV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboardsV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" "github.com/grafana/grafana/pkg/api/apierrors" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" @@ -36,7 +38,6 @@ import ( "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/web" - k8serrors "k8s.io/apimachinery/pkg/api/errors" ) const ( diff --git a/pkg/api/dtos/dashboard.go b/pkg/api/dtos/dashboard.go index ee0f67b798f..5d255537377 100644 --- a/pkg/api/dtos/dashboard.go +++ b/pkg/api/dtos/dashboard.go @@ -3,7 +3,7 @@ package dtos import ( "time" - dashboardsV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboardsV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" "github.com/grafana/grafana/pkg/components/simplejson" ) diff --git a/pkg/cmd/grafana-cli/commands/datamigrations/to_unified_storage.go b/pkg/cmd/grafana-cli/commands/datamigrations/to_unified_storage.go index ae54e9d12e1..293ec5d16fc 100644 --- a/pkg/cmd/grafana-cli/commands/datamigrations/to_unified_storage.go +++ b/pkg/cmd/grafana-cli/commands/datamigrations/to_unified_storage.go @@ -12,7 +12,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" authlib "github.com/grafana/authlib/types" - dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/cmd/grafana-cli/utils" diff --git a/pkg/registry/apis/dashboard/large.go b/pkg/registry/apis/dashboard/large.go index db465b5a191..c9c83f0dd37 100644 --- a/pkg/registry/apis/dashboard/large.go +++ b/pkg/registry/apis/dashboard/large.go @@ -7,7 +7,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" dashboardV0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" - dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" dashboardV2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" commonV0 "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/utils" diff --git a/pkg/registry/apis/dashboard/large_test.go b/pkg/registry/apis/dashboard/large_test.go index c9652bf2b59..9b86adf4011 100644 --- a/pkg/registry/apis/dashboard/large_test.go +++ b/pkg/registry/apis/dashboard/large_test.go @@ -10,7 +10,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" - dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" ) func TestLargeDashboardSupport(t *testing.T) { diff --git a/pkg/registry/apis/dashboard/legacy/migrate.go b/pkg/registry/apis/dashboard/legacy/migrate.go index de8cc5034b7..48db5f23930 100644 --- a/pkg/registry/apis/dashboard/legacy/migrate.go +++ b/pkg/registry/apis/dashboard/legacy/migrate.go @@ -11,7 +11,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" authlib "github.com/grafana/authlib/types" - dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/infra/db" diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go index e7067e82b95..b7a660f53e6 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go @@ -15,8 +15,8 @@ import ( claims "github.com/grafana/authlib/types" dashboardOG "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard" - dashboardv0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" - dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboardV0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" + dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/identity" @@ -43,7 +43,7 @@ type dashboardRow struct { RV int64 // Dashboard resource - Dash *dashboard.Dashboard + Dash *dashboardV1.Dashboard // The folder UID (needed for access control checks) FolderUID string @@ -222,8 +222,8 @@ func (r *rowsWrapper) Value() []byte { } func (a *dashboardSqlAccess) scanRow(rows *sql.Rows, history bool) (*dashboardRow, error) { - dash := &dashboard.Dashboard{ - TypeMeta: dashboard.DashboardResourceInfo.TypeMeta(), + dash := &dashboardV1.Dashboard{ + TypeMeta: dashboardV1.DashboardResourceInfo.TypeMeta(), ObjectMeta: metav1.ObjectMeta{Annotations: make(map[string]string)}, } row := &dashboardRow{Dash: dash} @@ -257,8 +257,11 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows, history bool) (*dashboardRo &updated, &updatedBy, &updatedByID, &version, &message, &data, &apiVersion, ) - if apiVersion.String == "" { - apiVersion.String = "v0alpha1" // default value + switch apiVersion.String { + case "": + apiVersion.String = dashboardV0.VERSION // default value + case "v1alpha1": + apiVersion.String = dashboardV0.VERSION // downgrade to v0 (it may not have run migrations) } row.token = &continueToken{orgId: orgId, id: dashboard_id} @@ -270,7 +273,7 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows, history bool) (*dashboardRo row.RV = version dash.ResourceVersion = fmt.Sprintf("%d", row.RV) dash.Namespace = a.namespacer(orgId) - dash.APIVersion = fmt.Sprintf("%s/%s", dashboard.GROUP, apiVersion.String) + dash.APIVersion = fmt.Sprintf("%s/%s", dashboardV1.GROUP, apiVersion.String) dash.UID = gapiutil.CalculateClusterWideUID(dash) dash.SetCreationTimestamp(metav1.NewTime(created)) meta, err := utils.MetaAccessor(dash) @@ -345,7 +348,7 @@ func getUserID(v sql.NullString, id sql.NullInt64) string { } // DeleteDashboard implements DashboardAccess. -func (a *dashboardSqlAccess) DeleteDashboard(ctx context.Context, orgId int64, uid string) (*dashboard.Dashboard, bool, error) { +func (a *dashboardSqlAccess) DeleteDashboard(ctx context.Context, orgId int64, uid string) (*dashboardV1.Dashboard, bool, error) { dash, _, err := a.GetDashboard(ctx, orgId, uid, 0) if err != nil { return nil, false, err @@ -361,7 +364,7 @@ func (a *dashboardSqlAccess) DeleteDashboard(ctx context.Context, orgId int64, u return dash, true, nil } -func (a *dashboardSqlAccess) buildSaveDashboardCommand(ctx context.Context, orgId int64, dash *dashboard.Dashboard) (*dashboards.SaveDashboardCommand, bool, error) { +func (a *dashboardSqlAccess) buildSaveDashboardCommand(ctx context.Context, orgId int64, dash *dashboardV1.Dashboard) (*dashboards.SaveDashboardCommand, bool, error) { created := false user, ok := claims.AuthInfoFrom(ctx) if !ok || user == nil { @@ -397,21 +400,21 @@ func (a *dashboardSqlAccess) buildSaveDashboardCommand(ctx context.Context, orgI } } - // v1 should be saved as schema version 41. v0 allows for older versions - if strings.HasSuffix(dash.APIVersion, "v1alpha1") { - schemaVersion := schemaversion.GetSchemaVersion(dash.Spec.Object) - if schemaVersion < int(schemaversion.LATEST_VERSION) { - dash.APIVersion = dashboardv0.VERSION - a.log.Info("Downgrading v1alpha1 dashboard to v0alpha1 due to schema version mismatch", "dashboard", dash.Name, "schema_version", schemaVersion) - } - } - - apiVersion := strings.TrimPrefix(dash.APIVersion, dashboard.GROUP+"/") + apiVersion := strings.TrimPrefix(dash.APIVersion, dashboardV1.GROUP+"/") meta, err := utils.MetaAccessor(dash) if err != nil { return nil, created, err } + // v1 should be saved as schema version 41. v0 allows for older versions + if strings.HasPrefix(apiVersion, "v1") { + schemaVersion := schemaversion.GetSchemaVersion(dash.Spec.Object) + if schemaVersion < int(schemaversion.LATEST_VERSION) { + apiVersion = dashboardV0.VERSION + a.log.Info("Downgrading v1alpha1 dashboard to v0alpha1 due to schema version mismatch", "dashboard", dash.Name, "schema_version", schemaVersion) + } + } + return &dashboards.SaveDashboardCommand{ OrgID: orgId, Message: meta.GetMessage(), @@ -424,7 +427,7 @@ func (a *dashboardSqlAccess) buildSaveDashboardCommand(ctx context.Context, orgI }, created, nil } -func (a *dashboardSqlAccess) SaveDashboard(ctx context.Context, orgId int64, dash *dashboard.Dashboard, failOnExisting bool) (*dashboard.Dashboard, bool, error) { +func (a *dashboardSqlAccess) SaveDashboard(ctx context.Context, orgId int64, dash *dashboardV1.Dashboard, failOnExisting bool) (*dashboardV1.Dashboard, bool, error) { user, ok := claims.AuthInfoFrom(ctx) if !ok || user == nil { return nil, false, fmt.Errorf("no user found in context") @@ -464,7 +467,7 @@ func (a *dashboardSqlAccess) SaveDashboard(ctx context.Context, orgId int64, das return dash, created, err } -func (a *dashboardSqlAccess) GetLibraryPanels(ctx context.Context, query LibraryPanelQuery) (*dashboard.LibraryPanelList, error) { +func (a *dashboardSqlAccess) GetLibraryPanels(ctx context.Context, query LibraryPanelQuery) (*dashboardV0.LibraryPanelList, error) { limit := int(query.Limit) query.Limit += 1 // for continue if query.OrgID == 0 { @@ -483,7 +486,7 @@ func (a *dashboardSqlAccess) GetLibraryPanels(ctx context.Context, query Library } q := rawQuery - res := &dashboard.LibraryPanelList{} + res := &dashboardV0.LibraryPanelList{} rows, err := sqlx.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...) defer func() { if rows != nil { @@ -524,9 +527,9 @@ func (a *dashboardSqlAccess) GetLibraryPanels(ctx context.Context, query Library } lastID = p.ID - item := dashboard.LibraryPanel{ + item := dashboardV0.LibraryPanel{ TypeMeta: metav1.TypeMeta{ - APIVersion: fmt.Sprintf("%s/%s", dashboard.GROUP, "v0alpha1"), + APIVersion: dashboardV0.APIVERSION, Kind: "LibraryPanel", }, ObjectMeta: metav1.ObjectMeta{ @@ -534,10 +537,10 @@ func (a *dashboardSqlAccess) GetLibraryPanels(ctx context.Context, query Library CreationTimestamp: metav1.NewTime(p.Created), ResourceVersion: strconv.FormatInt(p.Updated.UnixMicro(), 10), }, - Spec: dashboard.LibraryPanelSpec{}, + Spec: dashboardV0.LibraryPanelSpec{}, } - status := &dashboard.LibraryPanelStatus{ + status := &dashboardV0.LibraryPanelStatus{ Missing: v0alpha1.Unstructured{}, } err = json.Unmarshal(p.Model, &item.Spec) diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go index c3ee67f4fb8..6c19aa9ccf6 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go @@ -10,7 +10,8 @@ import ( "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboardV0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" + dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" @@ -134,20 +135,20 @@ func TestBuildSaveDashboardCommand(t *testing.T) { expectedAPI string }{ { - name: "with schema version 36 should save as v0alpha1", + name: "with schema version 36 should save as v0", schemaVersion: 36, - expectedAPI: "v0alpha1", - }, - { - name: "with schema version 41 should save as v1alpha1", - schemaVersion: 41, - expectedAPI: "v1alpha1", - }, - { - name: "with empty schema version should save as v0alpha1", - schemaVersion: 0, - expectedAPI: "v0alpha1", + expectedAPI: dashboardV0.VERSION, }, + // { + // name: "with schema version 41 should save as v1", + // schemaVersion: 41, + // expectedAPI: dashboardV1.VERSION, + // }, + // { + // name: "with empty schema version should save as v0", + // schemaVersion: 0, + // expectedAPI: dashboardV0.VERSION, + // }, } for _, tc := range testCases { @@ -167,9 +168,9 @@ func TestBuildSaveDashboardCommand(t *testing.T) { dashSpec["schemaVersion"] = tc.schemaVersion } - dash := &dashboard.Dashboard{ + dash := &dashboardV1.Dashboard{ TypeMeta: metav1.TypeMeta{ - APIVersion: dashboard.APIVERSION, + APIVersion: dashboardV1.APIVERSION, }, ObjectMeta: metav1.ObjectMeta{ Name: "test-dash", @@ -205,7 +206,7 @@ func TestBuildSaveDashboardCommand(t *testing.T) { &dashboards.Dashboard{ ID: 1234, Version: 2, - APIVersion: dashboard.APIVERSION, + APIVersion: dashboardV1.VERSION, }, nil).Once() cmd, created, err = access.buildSaveDashboardCommand(ctx, 1, dash) require.NoError(t, err) diff --git a/pkg/registry/apis/dashboard/legacy/storage.go b/pkg/registry/apis/dashboard/legacy/storage.go index 89254fb0f2f..42af196f49b 100644 --- a/pkg/registry/apis/dashboard/legacy/storage.go +++ b/pkg/registry/apis/dashboard/legacy/storage.go @@ -9,7 +9,7 @@ import ( claims "github.com/grafana/authlib/types" - dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/storage/unified/resource" diff --git a/pkg/registry/apis/dashboard/legacy/storage_test.go b/pkg/registry/apis/dashboard/legacy/storage_test.go index a4632aba459..1251d45ef2c 100644 --- a/pkg/registry/apis/dashboard/legacy/storage_test.go +++ b/pkg/registry/apis/dashboard/legacy/storage_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/infra/log" diff --git a/pkg/registry/apis/dashboard/legacy/types.go b/pkg/registry/apis/dashboard/legacy/types.go index 95b55aea94f..435103cac93 100644 --- a/pkg/registry/apis/dashboard/legacy/types.go +++ b/pkg/registry/apis/dashboard/legacy/types.go @@ -3,7 +3,8 @@ package legacy import ( "context" - dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboardV0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" + dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" "github.com/grafana/grafana/pkg/storage/unified/resource" ) @@ -54,10 +55,10 @@ type DashboardAccess interface { resource.ResourceIndexServer LegacyMigrator - GetDashboard(ctx context.Context, orgId int64, uid string, version int64) (*dashboard.Dashboard, int64, error) - SaveDashboard(ctx context.Context, orgId int64, dash *dashboard.Dashboard, failOnExisting bool) (*dashboard.Dashboard, bool, error) - DeleteDashboard(ctx context.Context, orgId int64, uid string) (*dashboard.Dashboard, bool, error) + GetDashboard(ctx context.Context, orgId int64, uid string, version int64) (*dashboardV1.Dashboard, int64, error) + SaveDashboard(ctx context.Context, orgId int64, dash *dashboardV1.Dashboard, failOnExisting bool) (*dashboardV1.Dashboard, bool, error) + DeleteDashboard(ctx context.Context, orgId int64, uid string) (*dashboardV1.Dashboard, bool, error) // Get a typed list - GetLibraryPanels(ctx context.Context, query LibraryPanelQuery) (*dashboard.LibraryPanelList, error) + GetLibraryPanels(ctx context.Context, query LibraryPanelQuery) (*dashboardV0.LibraryPanelList, error) } diff --git a/pkg/registry/apis/dashboard/legacysearcher/search_client.go b/pkg/registry/apis/dashboard/legacysearcher/search_client.go index 1724125f1a5..ef667736684 100644 --- a/pkg/registry/apis/dashboard/legacysearcher/search_client.go +++ b/pkg/registry/apis/dashboard/legacysearcher/search_client.go @@ -11,7 +11,7 @@ import ( "k8s.io/apimachinery/pkg/selection" claims "github.com/grafana/authlib/types" - dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" diff --git a/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go b/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go index 2f8a00f691c..cc3cd3c6869 100644 --- a/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go +++ b/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/selection" - dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/services/dashboards" diff --git a/pkg/registry/apis/dashboard/mutate.go b/pkg/registry/apis/dashboard/mutate.go index e2d0a02d116..2470d35b417 100644 --- a/pkg/registry/apis/dashboard/mutate.go +++ b/pkg/registry/apis/dashboard/mutate.go @@ -4,17 +4,17 @@ import ( "context" "fmt" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apiserver/pkg/admission" dashboardV0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" - dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" dashboardV2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" "github.com/grafana/grafana/apps/dashboard/pkg/migration" "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" "github.com/grafana/grafana/pkg/apimachinery/utils" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/validation/field" ) func (b *DashboardsAPIBuilder) Mutate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) { diff --git a/pkg/registry/apis/dashboard/mutation_test.go b/pkg/registry/apis/dashboard/mutation_test.go index 9865fb1828d..9eb092b8031 100644 --- a/pkg/registry/apis/dashboard/mutation_test.go +++ b/pkg/registry/apis/dashboard/mutation_test.go @@ -4,18 +4,19 @@ import ( "context" "testing" - dashv0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" - dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" - "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" - common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" - "github.com/grafana/grafana/pkg/apimachinery/utils" - "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/admission" + + dashv0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" + dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" + common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" + "github.com/grafana/grafana/pkg/services/featuremgmt" ) func TestDashboardAPIBuilder_Mutate(t *testing.T) { diff --git a/pkg/registry/apis/dashboard/register.go b/pkg/registry/apis/dashboard/register.go index 85a334dc405..7874457bbd2 100644 --- a/pkg/registry/apis/dashboard/register.go +++ b/pkg/registry/apis/dashboard/register.go @@ -22,7 +22,7 @@ import ( claims "github.com/grafana/authlib/types" internal "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard" dashv0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" - dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" dashv2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" "github.com/grafana/grafana/apps/dashboard/pkg/migration/conversion" "github.com/grafana/grafana/pkg/apimachinery/identity" diff --git a/pkg/registry/apis/dashboard/register_test.go b/pkg/registry/apis/dashboard/register_test.go index b149515e0ad..58ebb470cd9 100644 --- a/pkg/registry/apis/dashboard/register_test.go +++ b/pkg/registry/apis/dashboard/register_test.go @@ -5,18 +5,19 @@ import ( "fmt" "testing" - dashv0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" - dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" - dashv2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" - common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" - "github.com/grafana/grafana/pkg/services/dashboards" - "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/user" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/admission" + + dashv0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" + dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" + dashv2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/user" ) func TestDashboardAPIBuilder_Validate(t *testing.T) { diff --git a/pkg/registry/apis/dashboard/schema_validation.go b/pkg/registry/apis/dashboard/schema_validation.go index 7f09de98928..26749eaa598 100644 --- a/pkg/registry/apis/dashboard/schema_validation.go +++ b/pkg/registry/apis/dashboard/schema_validation.go @@ -5,16 +5,16 @@ import ( _ "embed" "fmt" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" - "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + v0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" + v1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" + v2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/services/featuremgmt" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // ValidateDashboardSpec validates the dashboard spec and throws a detailed error if there are validation errors. @@ -28,11 +28,11 @@ func (b *DashboardsAPIBuilder) ValidateDashboardSpec(ctx context.Context, obj ru mode := fieldValidationMode if mode != metav1.FieldValidationIgnore { switch obj.(type) { - case *v0alpha1.Dashboard: + case *v0.Dashboard: errorOnSchemaMismatches = false // Never error for v0 - case *v1alpha1.Dashboard: + case *v1.Dashboard: errorOnSchemaMismatches = !b.features.IsEnabled(ctx, featuremgmt.FlagDashboardDisableSchemaValidationV1) - case *v2alpha1.Dashboard: + case *v2.Dashboard: errorOnSchemaMismatches = !b.features.IsEnabled(ctx, featuremgmt.FlagDashboardDisableSchemaValidationV2) default: return nil, fmt.Errorf("invalid dashboard type: %T", obj) @@ -48,12 +48,12 @@ func (b *DashboardsAPIBuilder) ValidateDashboardSpec(ctx context.Context, obj ru var schemaVersionError field.ErrorList if errorOnSchemaMismatches || alwaysLogSchemaValidationErrors { switch v := obj.(type) { - case *v0alpha1.Dashboard: - errors, schemaVersionError = v0alpha1.ValidateDashboardSpec(v, alwaysLogSchemaValidationErrors) - case *v1alpha1.Dashboard: - errors, schemaVersionError = v1alpha1.ValidateDashboardSpec(v, alwaysLogSchemaValidationErrors) - case *v2alpha1.Dashboard: - errors = v2alpha1.ValidateDashboardSpec(v) + case *v0.Dashboard: + errors, schemaVersionError = v0.ValidateDashboardSpec(v, alwaysLogSchemaValidationErrors) + case *v1.Dashboard: + errors, schemaVersionError = v1.ValidateDashboardSpec(v, alwaysLogSchemaValidationErrors) + case *v2.Dashboard: + errors = v2.ValidateDashboardSpec(v) } } diff --git a/pkg/registry/apis/dashboard/search.go b/pkg/registry/apis/dashboard/search.go index a5e2b50198a..ca61ca5eb27 100644 --- a/pkg/registry/apis/dashboard/search.go +++ b/pkg/registry/apis/dashboard/search.go @@ -18,9 +18,6 @@ import ( "k8s.io/kube-openapi/pkg/spec3" "k8s.io/kube-openapi/pkg/validation/spec" - "github.com/grafana/grafana/pkg/storage/legacysql/dualwrite" - "github.com/grafana/grafana/pkg/storage/unified/search" - dashboardv0alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" "github.com/grafana/grafana/pkg/apimachinery/identity" @@ -30,7 +27,9 @@ import ( dashboardsearch "github.com/grafana/grafana/pkg/services/dashboards/service/search" "github.com/grafana/grafana/pkg/services/featuremgmt" foldermodel "github.com/grafana/grafana/pkg/services/folder" + "github.com/grafana/grafana/pkg/storage/legacysql/dualwrite" "github.com/grafana/grafana/pkg/storage/unified/resource" + "github.com/grafana/grafana/pkg/storage/unified/search" "github.com/grafana/grafana/pkg/util/errhttp" ) diff --git a/pkg/registry/apis/provisioning/jobs/pullrequest/changes.go b/pkg/registry/apis/provisioning/jobs/pullrequest/changes.go index 227f6f52fff..4bd6d5bf844 100644 --- a/pkg/registry/apis/provisioning/jobs/pullrequest/changes.go +++ b/pkg/registry/apis/provisioning/jobs/pullrequest/changes.go @@ -8,7 +8,7 @@ import ( "strings" "github.com/grafana/grafana-app-sdk/logging" - dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" "github.com/grafana/grafana/pkg/cmd/grafana-cli/logger" "github.com/grafana/grafana/pkg/infra/slugify" diff --git a/pkg/registry/apis/provisioning/resources/client.go b/pkg/registry/apis/provisioning/resources/client.go index 6f32bc87a39..fcdabcb0705 100644 --- a/pkg/registry/apis/provisioning/resources/client.go +++ b/pkg/registry/apis/provisioning/resources/client.go @@ -10,7 +10,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" - dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" iam "github.com/grafana/grafana/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/services/apiserver" diff --git a/pkg/registry/apis/provisioning/resources/parser_test.go b/pkg/registry/apis/provisioning/resources/parser_test.go index bf545f90b07..d718445fc57 100644 --- a/pkg/registry/apis/provisioning/resources/parser_test.go +++ b/pkg/registry/apis/provisioning/resources/parser_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" dashboardV0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" - dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository" ) diff --git a/pkg/services/apiserver/standalone/runtime_test.go b/pkg/services/apiserver/standalone/runtime_test.go index dbee0a54e2b..08dca9505bc 100644 --- a/pkg/services/apiserver/standalone/runtime_test.go +++ b/pkg/services/apiserver/standalone/runtime_test.go @@ -4,16 +4,17 @@ import ( "fmt" "testing" - dashboardv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" "github.com/stretchr/testify/require" + + dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" ) func TestReadRuntimeCOnfig(t *testing.T) { - out, err := ReadRuntimeConfig("all/all=true," + dashboardv1.APIVERSION + "=false") + out, err := ReadRuntimeConfig("all/all=true," + dashboardV1.APIVERSION + "=false") require.NoError(t, err) require.Equal(t, []RuntimeConfig{ {Group: "all", Version: "all", Enabled: true}, - {Group: "dashboard.grafana.app", Version: "v1alpha1", Enabled: false}, + {Group: dashboardV1.GROUP, Version: dashboardV1.VERSION, Enabled: false}, }, out) require.Equal(t, "all/all=true", fmt.Sprintf("%v", out[0])) diff --git a/pkg/services/authz/zanzana/common/tuple.go b/pkg/services/authz/zanzana/common/tuple.go index d0a550107fc..41ed7c2a85b 100644 --- a/pkg/services/authz/zanzana/common/tuple.go +++ b/pkg/services/authz/zanzana/common/tuple.go @@ -6,7 +6,7 @@ import ( openfgav1 "github.com/openfga/api/proto/openfga/v1" "google.golang.org/protobuf/types/known/structpb" - dashboardalpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" "github.com/grafana/grafana/pkg/apimachinery/utils" authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1" ) @@ -377,8 +377,8 @@ func AddRenderContext(req *openfgav1.CheckRequest) { User: req.TupleKey.User, Relation: RelationSetView, Object: NewGroupResourceIdent( - dashboardalpha1.DashboardResourceInfo.GroupResource().Group, - dashboardalpha1.DashboardResourceInfo.GroupResource().Resource, + dashboardV1.DashboardResourceInfo.GroupResource().Group, + dashboardV1.DashboardResourceInfo.GroupResource().Resource, "", ), }) diff --git a/pkg/services/authz/zanzana/translations.go b/pkg/services/authz/zanzana/translations.go index f7002da9ec2..0abefcf2e29 100644 --- a/pkg/services/authz/zanzana/translations.go +++ b/pkg/services/authz/zanzana/translations.go @@ -1,7 +1,7 @@ package zanzana import ( - dashboards "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboards "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" ) diff --git a/pkg/services/dashboards/service/client/client.go b/pkg/services/dashboards/service/client/client.go index dd9d306d97d..401d4773d16 100644 --- a/pkg/services/dashboards/service/client/client.go +++ b/pkg/services/dashboards/service/client/client.go @@ -5,6 +5,7 @@ import ( "fmt" "sync" + "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel/attribute" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -22,7 +23,6 @@ import ( "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/legacysql/dualwrite" "github.com/grafana/grafana/pkg/storage/unified/resource" - "github.com/prometheus/client_golang/prometheus" ) type K8sClientFactory func(ctx context.Context, version string) client.K8sHandler diff --git a/pkg/services/dashboards/service/client/client_test.go b/pkg/services/dashboards/service/client/client_test.go index 767f89ad1f5..0e05ed11c72 100644 --- a/pkg/services/dashboards/service/client/client_test.go +++ b/pkg/services/dashboards/service/client/client_test.go @@ -11,7 +11,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - dashboardv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboardv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/apiserver/client" ) diff --git a/pkg/services/dashboards/service/dashboard_service_test.go b/pkg/services/dashboards/service/dashboard_service_test.go index bf2b3a0f5e2..2861055a667 100644 --- a/pkg/services/dashboards/service/dashboard_service_test.go +++ b/pkg/services/dashboards/service/dashboard_service_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "reflect" + "slices" "testing" "time" @@ -44,7 +45,6 @@ import ( "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/legacysql/dualwrite" "github.com/grafana/grafana/pkg/storage/unified/resource" - "golang.org/x/exp/slices" ) func TestDashboardService(t *testing.T) { diff --git a/pkg/services/folder/folderimpl/folder.go b/pkg/services/folder/folderimpl/folder.go index 5616096b6f4..389d939be1e 100644 --- a/pkg/services/folder/folderimpl/folder.go +++ b/pkg/services/folder/folderimpl/folder.go @@ -18,7 +18,7 @@ import ( "github.com/grafana/dskit/concurrency" - dashboardv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboardv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" folderv1 "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/bus" diff --git a/pkg/services/provisioning/dashboards/dashboard.go b/pkg/services/provisioning/dashboards/dashboard.go index 0c7d92d6915..c1656349adf 100644 --- a/pkg/services/provisioning/dashboards/dashboard.go +++ b/pkg/services/provisioning/dashboards/dashboard.go @@ -6,7 +6,7 @@ import ( "os" "time" - dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/folder" diff --git a/pkg/storage/legacysql/dualwrite/utils.go b/pkg/storage/legacysql/dualwrite/utils.go index 27e36a16213..12d1877a391 100644 --- a/pkg/storage/legacysql/dualwrite/utils.go +++ b/pkg/storage/legacysql/dualwrite/utils.go @@ -4,7 +4,7 @@ import ( "golang.org/x/net/context" "k8s.io/apimachinery/pkg/runtime/schema" - dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" ) diff --git a/pkg/storage/legacysql/dualwrite/utils_test.go b/pkg/storage/legacysql/dualwrite/utils_test.go index fd4ba1efd3e..67fdf654b63 100644 --- a/pkg/storage/legacysql/dualwrite/utils_test.go +++ b/pkg/storage/legacysql/dualwrite/utils_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/runtime/schema" - dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" ) diff --git a/pkg/storage/unified/apistore/fake_large.go b/pkg/storage/unified/apistore/fake_large.go index f222e8470d2..b92d4bbed12 100644 --- a/pkg/storage/unified/apistore/fake_large.go +++ b/pkg/storage/unified/apistore/fake_large.go @@ -3,10 +3,11 @@ package apistore import ( "context" - dashboardv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + "k8s.io/apimachinery/pkg/runtime/schema" + + dashboardv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/storage/unified/resource" - "k8s.io/apimachinery/pkg/runtime/schema" ) type LargeObjectSupportFake struct { diff --git a/pkg/storage/unified/apistore/managed_test.go b/pkg/storage/unified/apistore/managed_test.go index 7623db8f473..24c0b3522de 100644 --- a/pkg/storage/unified/apistore/managed_test.go +++ b/pkg/storage/unified/apistore/managed_test.go @@ -10,7 +10,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" authtypes "github.com/grafana/authlib/types" - dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" ) diff --git a/pkg/storage/unified/apistore/prepare_test.go b/pkg/storage/unified/apistore/prepare_test.go index b503ee6436d..8845ce778cc 100644 --- a/pkg/storage/unified/apistore/prepare_test.go +++ b/pkg/storage/unified/apistore/prepare_test.go @@ -16,7 +16,7 @@ import ( "k8s.io/apiserver/pkg/storage" authtypes "github.com/grafana/authlib/types" - dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" ) diff --git a/pkg/storage/unified/resource/search.go b/pkg/storage/unified/resource/search.go index 858e67f7450..0f8da3f4a78 100644 --- a/pkg/storage/unified/resource/search.go +++ b/pkg/storage/unified/resource/search.go @@ -17,7 +17,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "github.com/grafana/authlib/types" - dashboardv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboardv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" ) diff --git a/pkg/storage/unified/search/dashboard.go b/pkg/storage/unified/search/dashboard.go index a4b2275ad32..fc44c4cb034 100644 --- a/pkg/storage/unified/search/dashboard.go +++ b/pkg/storage/unified/search/dashboard.go @@ -8,7 +8,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - dashV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/services/store/kind/dashboard" "github.com/grafana/grafana/pkg/storage/unified/resource" diff --git a/pkg/tests/apis/dashboard/dashboards_test.go b/pkg/tests/apis/dashboard/dashboards_test.go index 3e4c75f11cd..c107eacb2c9 100644 --- a/pkg/tests/apis/dashboard/dashboards_test.go +++ b/pkg/tests/apis/dashboard/dashboards_test.go @@ -20,7 +20,7 @@ import ( "github.com/grafana/grafana/pkg/tests/testsuite" dashboardV0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" - dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" dashboardV2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" ) @@ -112,8 +112,8 @@ func runDashboardTest(t *testing.T, helper *apis.K8sTestHelper, gvr schema.Group func TestIntegrationDashboardsAppV0Alpha1(t *testing.T) { gvr := schema.GroupVersionResource{ - Group: "dashboard.grafana.app", - Version: "v0alpha1", + Group: dashboardV1.GROUP, + Version: dashboardV1.VERSION, Resource: "dashboards", } if testing.Short() { @@ -182,17 +182,17 @@ func TestIntegrationDashboardsAppV0Alpha1(t *testing.T) { }) } -func TestIntegrationDashboardsAppV1Alpha1(t *testing.T) { +func TestIntegrationDashboardsAppV1(t *testing.T) { gvr := schema.GroupVersionResource{ - Group: "dashboard.grafana.app", - Version: "v1alpha1", + Group: dashboardV1.GROUP, + Version: dashboardV1.VERSION, Resource: "dashboards", } if testing.Short() { t.Skip("skipping integration test") } - t.Run("v1alpha1 with dual writer mode 0", func(t *testing.T) { + t.Run("v1 with dual writer mode 0", func(t *testing.T) { helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ DisableAnonymous: true, UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ @@ -204,7 +204,7 @@ func TestIntegrationDashboardsAppV1Alpha1(t *testing.T) { runDashboardTest(t, helper, gvr) }) - t.Run("v1alpha1 with dual writer mode 1", func(t *testing.T) { + t.Run("v1 with dual writer mode 1", func(t *testing.T) { helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ DisableAnonymous: true, UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ @@ -216,7 +216,7 @@ func TestIntegrationDashboardsAppV1Alpha1(t *testing.T) { runDashboardTest(t, helper, gvr) }) - t.Run("v1alpha1 with dual writer mode 2", func(t *testing.T) { + t.Run("v1 with dual writer mode 2", func(t *testing.T) { helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ DisableAnonymous: true, UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ @@ -228,7 +228,7 @@ func TestIntegrationDashboardsAppV1Alpha1(t *testing.T) { runDashboardTest(t, helper, gvr) }) - t.Run("v1alpha1 with dual writer mode 3", func(t *testing.T) { + t.Run("v1 with dual writer mode 3", func(t *testing.T) { helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ DisableAnonymous: true, UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ @@ -240,7 +240,7 @@ func TestIntegrationDashboardsAppV1Alpha1(t *testing.T) { runDashboardTest(t, helper, gvr) }) - t.Run("v1alpha1 with dual writer mode 4", func(t *testing.T) { + t.Run("v1 with dual writer mode 4", func(t *testing.T) { t.Skip("skipping test because of authorizer issue") helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ DisableAnonymous: true, @@ -343,14 +343,14 @@ func TestIntegrationLegacySupport(t *testing.T) { Path: "/api/dashboards/uid/test-v0", }, &dtos.DashboardFullWithMeta{}) require.Equal(t, 200, rsp.Response.StatusCode) - require.Equal(t, "v0alpha1", rsp.Result.Meta.APIVersion) + require.Equal(t, dashboardV0.VERSION, rsp.Result.Meta.APIVersion) rsp = apis.DoRequest(helper, apis.RequestParams{ User: helper.Org1.Admin, Path: "/api/dashboards/uid/test-v1", }, &dtos.DashboardFullWithMeta{}) require.Equal(t, 200, rsp.Response.StatusCode) - require.Equal(t, "v0alpha1", rsp.Result.Meta.APIVersion) // v0alpha1 is used as the default version for /api + require.Equal(t, dashboardV0.VERSION, rsp.Result.Meta.APIVersion) // V2 should send a not acceptable rsp = apis.DoRequest(helper, apis.RequestParams{ diff --git a/pkg/tests/apis/dashboard/integration/api_validation_test.go b/pkg/tests/apis/dashboard/integration/api_validation_test.go index 37db48714a1..3992ad69f71 100644 --- a/pkg/tests/apis/dashboard/integration/api_validation_test.go +++ b/pkg/tests/apis/dashboard/integration/api_validation_test.go @@ -9,10 +9,15 @@ import ( "strings" "testing" - dashboardv0alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" - dashboardv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" - dashboardv2alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" - folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" + "github.com/stretchr/testify/require" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + + dashboardV0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" + dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" + dashboardV2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + foldersV1 "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" @@ -21,10 +26,6 @@ import ( "github.com/grafana/grafana/pkg/tests/apis" "github.com/grafana/grafana/pkg/tests/testinfra" "github.com/grafana/grafana/pkg/tests/testsuite" - "github.com/stretchr/testify/require" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" @@ -258,11 +259,11 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { }{ { name: "v0alpha1 dashboard with wrong spec should not throw on v0", - resourceInfo: dashboardv0alpha1.DashboardResourceInfo, + resourceInfo: dashboardV0.DashboardResourceInfo, expectSpecErr: false, testObject: &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": dashboardv0alpha1.DashboardResourceInfo.TypeMeta().APIVersion, + "apiVersion": dashboardV0.DashboardResourceInfo.TypeMeta().APIVersion, "kind": "Dashboard", "metadata": map[string]interface{}{ "generateName": "test-", @@ -279,11 +280,11 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { }, { name: "v1 dashboard with wrong spec should throw on v1", - resourceInfo: dashboardv1.DashboardResourceInfo, + resourceInfo: dashboardV1.DashboardResourceInfo, expectSpecErr: true, testObject: &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": dashboardv1.DashboardResourceInfo.TypeMeta().APIVersion, + "apiVersion": dashboardV1.DashboardResourceInfo.TypeMeta().APIVersion, "kind": "Dashboard", "metadata": map[string]interface{}{ "generateName": "test-", @@ -300,11 +301,11 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { }, { name: "v2alpha1 dashboard with correct spec should not throw on v2", - resourceInfo: dashboardv2alpha1.DashboardResourceInfo, + resourceInfo: dashboardV2.DashboardResourceInfo, expectSpecErr: false, testObject: &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": dashboardv2alpha1.DashboardResourceInfo.TypeMeta().APIVersion, + "apiVersion": dashboardV2.DashboardResourceInfo.TypeMeta().APIVersion, "kind": "Dashboard", "metadata": map[string]interface{}{ "generateName": "test-", @@ -786,12 +787,12 @@ func createTestContext(t *testing.T, helper *apis.K8sTestHelper, orgUsers apis.O // getDashboardGVR returns the dashboard GroupVersionResource func getDashboardGVR() schema.GroupVersionResource { - return dashboardv1.DashboardResourceInfo.GroupVersionResource() + return dashboardV1.DashboardResourceInfo.GroupVersionResource() } // getFolderGVR returns the folder GroupVersionResource func getFolderGVR() schema.GroupVersionResource { - return folders.FolderResourceInfo.GroupVersionResource() + return foldersV1.FolderResourceInfo.GroupVersionResource() } // Get a resource client for the specified user @@ -822,8 +823,8 @@ func createFolderObject(t *testing.T, title string, namespace string, parentFold folderObj := &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": folders.FolderResourceInfo.GroupVersion().String(), - "kind": folders.FolderResourceInfo.GroupVersionKind().Kind, + "apiVersion": foldersV1.FolderResourceInfo.GroupVersion().String(), + "kind": foldersV1.FolderResourceInfo.GroupVersionKind().Kind, "metadata": map[string]interface{}{ "generateName": "test-folder-", "namespace": namespace, @@ -878,8 +879,8 @@ func createDashboardObject(t *testing.T, title string, folderUID string, generat dashObj := &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": dashboardv1.DashboardResourceInfo.GroupVersion().String(), - "kind": dashboardv1.DashboardResourceInfo.GroupVersionKind().Kind, + "apiVersion": dashboardV1.DashboardResourceInfo.GroupVersion().String(), + "kind": dashboardV1.DashboardResourceInfo.GroupVersionKind().Kind, "metadata": map[string]interface{}{ "generateName": "test-", "annotations": map[string]interface{}{ @@ -1464,7 +1465,7 @@ func runDashboardPermissionTests(t *testing.T, ctx TestContext) { //statusErr := ctx.Helper.AsStatusError(err) //require.Equal(t, http.StatusNotFound, int(statusErr.Status().Code), "Should get 404 Not Found") // TODO: Find out why this throws a 500 instead of a 404 with this message: - // an error on the server (\"Internal Server Error: \\\"/apis/dashboard.grafana.app/v1alpha1/namespaces/org-3/dashboards/test-cs6xk\\\": Dashboard not found\") has prevented the request from succeeding" + // an error on the server (\"Internal Server Error: \\\"/apis/dashboard.grafana.app/v1beta1/namespaces/org-3/dashboards/test-cs6xk\\\": Dashboard not found\") has prevented the request from succeeding" // Clean up err = adminClient.Resource.Delete(context.Background(), org1DashUID, v1.DeleteOptions{}) @@ -1649,7 +1650,7 @@ func runCrossOrgTests(t *testing.T, org1Ctx, org2Ctx TestContext) { require.Error(t, err, "Should not be able to access dashboard from another org") //statusErr := org1Ctx.Helper.AsStatusError(err) // TODO: Find out why this throws a 500 instead of a 404 with this message: - // "an error on the server (\"Internal Server Error: \\\"/apis/dashboard.grafana.app/v1alpha1/namespaces/default/dashboards/test-rbm2q\\\": Dashboard not found\") has prevented the request from succeeding" + // "an error on the server (\"Internal Server Error: \\\"/apis/dashboard.grafana.app/v1beta1/namespaces/default/dashboards/test-rbm2q\\\": Dashboard not found\") has prevented the request from succeeding" //require.Equal(t, http.StatusNotFound, int(statusErr.Status().Code), "Should get 404 Not Found") // Get a dashboard as admin from the target org to then send an update request @@ -1871,7 +1872,7 @@ func runDashboardHttpTest(t *testing.T, ctx TestContext, foreignOrgCtx TestConte "POST", locTC.name, userTC.name) // Construct the dashboard URL - dashboardPath := fmt.Sprintf("/apis/dashboard.grafana.app/v1alpha1/namespaces/%s/dashboards", ctx.Helper.Namespacer(ctx.OrgID)) + dashboardPath := fmt.Sprintf("/apis/dashboard.grafana.app/v1beta1/namespaces/%s/dashboards", ctx.Helper.Namespacer(ctx.OrgID)) // Create dashboard JSON with a single template var metadata string @@ -1884,7 +1885,7 @@ func runDashboardHttpTest(t *testing.T, ctx TestContext, foreignOrgCtx TestConte dashboardJSON := fmt.Sprintf(`{ "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v1alpha1", + "apiVersion": "dashboard.grafana.app/v1beta1", "metadata": { %s }, @@ -1915,7 +1916,7 @@ func runDashboardHttpTest(t *testing.T, ctx TestContext, foreignOrgCtx TestConte "Failed to %s dashboard as %s: %s", "POST", userTC.user.Identity.GetLogin(), createResp.Response.Status) // Construct the dashboard path with the actual UID for GET/DELETE - dashboardPath = fmt.Sprintf("/apis/dashboard.grafana.app/v1alpha1/namespaces/%s/dashboards/%s", + dashboardPath = fmt.Sprintf("/apis/dashboard.grafana.app/v1beta1/namespaces/%s/dashboards/%s", ctx.Helper.Namespacer(ctx.OrgID), dashboardUID) // Verify the dashboard was created by getting it via the admin client diff --git a/pkg/tests/apis/dashboard/testdata/dashboard-test-v1.yaml b/pkg/tests/apis/dashboard/testdata/dashboard-test-v1.yaml index 6ad80f602c6..18dee5c28d9 100644 --- a/pkg/tests/apis/dashboard/testdata/dashboard-test-v1.yaml +++ b/pkg/tests/apis/dashboard/testdata/dashboard-test-v1.yaml @@ -1,4 +1,4 @@ -apiVersion: dashboard.grafana.app/v1alpha1 +apiVersion: dashboard.grafana.app/v1beta1 kind: Dashboard metadata: name: test-v1 diff --git a/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v1alpha1.json b/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v1beta1.json similarity index 95% rename from pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v1alpha1.json rename to pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v1beta1.json index 1aaa37acfa1..b93e2c4642e 100644 --- a/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v1alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v1beta1.json @@ -2,10 +2,10 @@ "openapi": "3.0.0", "info": { "description": "Grafana dashboards as resources", - "title": "dashboard.grafana.app/v1alpha1" + "title": "dashboard.grafana.app/v1beta1" }, "paths": { - "/apis/dashboard.grafana.app/v1alpha1/": { + "/apis/dashboard.grafana.app/v1beta1/": { "get": { "tags": [ "API Discovery" @@ -36,7 +36,7 @@ } } }, - "/apis/dashboard.grafana.app/v1alpha1/namespaces/{namespace}/dashboards": { + "/apis/dashboard.grafana.app/v1beta1/namespaces/{namespace}/dashboards": { "get": { "tags": [ "Dashboard" @@ -141,27 +141,27 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.DashboardList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.DashboardList" } }, "application/json;stream=watch": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.DashboardList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.DashboardList" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.DashboardList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.DashboardList" } }, "application/vnd.kubernetes.protobuf;stream=watch": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.DashboardList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.DashboardList" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.DashboardList" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.DashboardList" } } } @@ -170,7 +170,7 @@ "x-kubernetes-action": "list", "x-kubernetes-group-version-kind": { "group": "dashboard.grafana.app", - "version": "v1alpha1", + "version": "v1beta1", "kind": "Dashboard" } }, @@ -213,17 +213,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } } }, @@ -235,17 +235,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } } } @@ -255,17 +255,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } } } @@ -275,17 +275,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } } } @@ -294,7 +294,7 @@ "x-kubernetes-action": "post", "x-kubernetes-group-version-kind": { "group": "dashboard.grafana.app", - "version": "v1alpha1", + "version": "v1beta1", "kind": "Dashboard" } }, @@ -448,7 +448,7 @@ "x-kubernetes-action": "deletecollection", "x-kubernetes-group-version-kind": { "group": "dashboard.grafana.app", - "version": "v1alpha1", + "version": "v1beta1", "kind": "Dashboard" } }, @@ -474,7 +474,7 @@ } ] }, - "/apis/dashboard.grafana.app/v1alpha1/namespaces/{namespace}/dashboards/{name}": { + "/apis/dashboard.grafana.app/v1beta1/namespaces/{namespace}/dashboards/{name}": { "get": { "tags": [ "Dashboard" @@ -487,17 +487,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } } } @@ -506,7 +506,7 @@ "x-kubernetes-action": "get", "x-kubernetes-group-version-kind": { "group": "dashboard.grafana.app", - "version": "v1alpha1", + "version": "v1beta1", "kind": "Dashboard" } }, @@ -549,17 +549,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } } }, @@ -571,17 +571,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } } } @@ -591,17 +591,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } } } @@ -610,7 +610,7 @@ "x-kubernetes-action": "put", "x-kubernetes-group-version-kind": { "group": "dashboard.grafana.app", - "version": "v1alpha1", + "version": "v1beta1", "kind": "Dashboard" } }, @@ -712,7 +712,7 @@ "x-kubernetes-action": "delete", "x-kubernetes-group-version-kind": { "group": "dashboard.grafana.app", - "version": "v1alpha1", + "version": "v1beta1", "kind": "Dashboard" } }, @@ -791,17 +791,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } } } @@ -811,17 +811,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } }, "application/vnd.kubernetes.protobuf": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } }, "application/yaml": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } } } @@ -830,7 +830,7 @@ "x-kubernetes-action": "patch", "x-kubernetes-group-version-kind": { "group": "dashboard.grafana.app", - "version": "v1alpha1", + "version": "v1beta1", "kind": "Dashboard" } }, @@ -866,7 +866,7 @@ } ] }, - "/apis/dashboard.grafana.app/v1alpha1/namespaces/{namespace}/dashboards/{name}/dto": { + "/apis/dashboard.grafana.app/v1beta1/namespaces/{namespace}/dashboards/{name}/dto": { "get": { "tags": [ "Dashboard" @@ -879,7 +879,7 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.DashboardWithAccessInfo" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.DashboardWithAccessInfo" } } } @@ -888,7 +888,7 @@ "x-kubernetes-action": "connect", "x-kubernetes-group-version-kind": { "group": "dashboard.grafana.app", - "version": "v1alpha1", + "version": "v1beta1", "kind": "DashboardWithAccessInfo" } }, @@ -918,7 +918,7 @@ }, "components": { "schemas": { - "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.AnnotationActions": { + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.AnnotationActions": { "type": "object", "required": [ "canAdd", @@ -940,7 +940,7 @@ } } }, - "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.AnnotationPermission": { + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.AnnotationPermission": { "type": "object", "required": [ "dashboard", @@ -951,7 +951,7 @@ "default": {}, "allOf": [ { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.AnnotationActions" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.AnnotationActions" } ] }, @@ -959,13 +959,13 @@ "default": {}, "allOf": [ { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.AnnotationActions" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.AnnotationActions" } ] } } }, - "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard": { + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard": { "type": "object", "required": [ "metadata", @@ -1001,7 +1001,7 @@ "default": {}, "allOf": [ { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.DashboardStatus" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.DashboardStatus" } ] } @@ -1010,11 +1010,11 @@ { "group": "dashboard.grafana.app", "kind": "Dashboard", - "version": "v1alpha1" + "version": "v1beta1" } ] }, - "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.DashboardAccess": { + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.DashboardAccess": { "description": "Information about how the requesting user can use a given dashboard", "type": "object", "required": [ @@ -1027,7 +1027,7 @@ ], "properties": { "annotationsPermissions": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.AnnotationPermission" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.AnnotationPermission" }, "canAdmin": { "type": "boolean", @@ -1059,7 +1059,7 @@ } } }, - "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.DashboardConversionStatus": { + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.DashboardConversionStatus": { "description": "ConversionStatus is the status of the conversion of the dashboard.", "type": "object", "required": [ @@ -1085,7 +1085,7 @@ } } }, - "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.DashboardList": { + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.DashboardList": { "type": "object", "required": [ "metadata", @@ -1102,7 +1102,7 @@ "default": {}, "allOf": [ { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.Dashboard" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.Dashboard" } ] } @@ -1124,24 +1124,24 @@ { "group": "dashboard.grafana.app", "kind": "DashboardList", - "version": "v1alpha1" + "version": "v1beta1" } ] }, - "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.DashboardStatus": { + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.DashboardStatus": { "type": "object", "properties": { "conversion": { "description": "Optional conversion status.", "allOf": [ { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.DashboardConversionStatus" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.DashboardConversionStatus" } ] } } }, - "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.DashboardWithAccessInfo": { + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.DashboardWithAccessInfo": { "description": "This is like the legacy DTO where access and metadata are all returned in a single call", "type": "object", "required": [ @@ -1155,7 +1155,7 @@ "default": {}, "allOf": [ { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.DashboardAccess" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.DashboardAccess" } ] }, @@ -1187,7 +1187,7 @@ "default": {}, "allOf": [ { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1alpha1.DashboardStatus" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v1beta1.DashboardStatus" } ] } @@ -1196,7 +1196,7 @@ { "group": "dashboard.grafana.app", "kind": "DashboardWithAccessInfo", - "version": "v1alpha1" + "version": "v1beta1" } ] }, diff --git a/pkg/tests/apis/openapi_test.go b/pkg/tests/apis/openapi_test.go index bfb7c14aeac..05ed40bdd34 100644 --- a/pkg/tests/apis/openapi_test.go +++ b/pkg/tests/apis/openapi_test.go @@ -71,7 +71,7 @@ func TestIntegrationOpenAPIs(t *testing.T) { Version: "v0alpha1", }, { Group: "dashboard.grafana.app", - Version: "v1alpha1", + Version: "v1beta1", }, { Group: "dashboard.grafana.app", Version: "v2alpha1", diff --git a/pkg/tests/apis/provisioning/client_test.go b/pkg/tests/apis/provisioning/client_test.go index af46419d983..7f11699e3ab 100644 --- a/pkg/tests/apis/provisioning/client_test.go +++ b/pkg/tests/apis/provisioning/client_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/runtime/schema" + dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" ) @@ -24,22 +25,24 @@ func TestIntegrationProvisioning_Client(t *testing.T) { t.Run("dashboard client support", func(t *testing.T) { _, _, err := clients.ForResource(schema.GroupVersionResource{ - Group: "dashboard.grafana.app", + Group: dashboardV1.GROUP, + Version: dashboardV1.VERSION, Resource: "dashboards", - Version: "v1alpha1", }) require.NoError(t, err) - // With empty version, we should get the preferred version (v1alpha1) - _, _, err = clients.ForResource(schema.GroupVersionResource{ - Group: "dashboard.grafana.app", + // With empty version, we should get the preferred version (v1beta1) + _, gvk, err := clients.ForResource(schema.GroupVersionResource{ + Group: dashboardV1.GROUP, Resource: "dashboards", }) require.NoError(t, err) + require.Equal(t, dashboardV1.VERSION, gvk.Version) + require.Equal(t, "Dashboard", gvk.Kind) _, _, err = clients.ForKind(schema.GroupVersionKind{ - Group: "dashboard.grafana.app", - Version: "v1alpha1", + Group: dashboardV1.GROUP, + Version: dashboardV1.VERSION, Kind: "Dashboard", }) require.NoError(t, err) diff --git a/pkg/tests/apis/provisioning/exportunifiedtorepository/root_dashboard.json b/pkg/tests/apis/provisioning/exportunifiedtorepository/root_dashboard.json index a144c917c64..b2008d618b0 100644 --- a/pkg/tests/apis/provisioning/exportunifiedtorepository/root_dashboard.json +++ b/pkg/tests/apis/provisioning/exportunifiedtorepository/root_dashboard.json @@ -1,5 +1,5 @@ { - "apiVersion": "dashboard.grafana.app/v1alpha1", + "apiVersion": "dashboard.grafana.app/v1beta1", "kind": "Dashboard", "metadata": { "name": "root_dashboard", diff --git a/pkg/tests/apis/provisioning/helper_test.go b/pkg/tests/apis/provisioning/helper_test.go index 66b76bc2a65..e7226447fd1 100644 --- a/pkg/tests/apis/provisioning/helper_test.go +++ b/pkg/tests/apis/provisioning/helper_test.go @@ -23,7 +23,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/rest" - dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" + dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" folder "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts index e8ebbd08d02..0721d2f32cf 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts @@ -1235,7 +1235,7 @@ const v1ProvisionedDashboardResource = { resource: { type: { group: 'dashboard.grafana.app', - version: 'v1alpha1', + version: 'v1beta1', kind: 'Dashboard', resource: 'dashboards', }, @@ -1243,7 +1243,7 @@ const v1ProvisionedDashboardResource = { existing: {}, action: 'update', dryRun: { - apiVersion: 'dashboard.grafana.app/v1alpha1', + apiVersion: 'dashboard.grafana.app/v1beta1', kind: 'Dashboard', metadata: { annotations: { diff --git a/public/app/features/dashboard/api/types.ts b/public/app/features/dashboard/api/types.ts index 8735cd23ddd..e9c66c24cfc 100644 --- a/public/app/features/dashboard/api/types.ts +++ b/public/app/features/dashboard/api/types.ts @@ -34,7 +34,7 @@ export interface DashboardVersionError extends Error { status: number; data: { // The version which was stored when the dashboard was created / updated. - // Currently known versions are: 'v2alpha1' | 'v1alpha1' | 'v0alpha1' + // Currently known versions are: 'v2alpha1' | 'v1beta1' | 'v0alpha1' storedVersion: string; message: string; }; diff --git a/public/app/features/dashboard/api/v1.test.ts b/public/app/features/dashboard/api/v1.test.ts index 46c1fc47ef6..c0beae229b8 100644 --- a/public/app/features/dashboard/api/v1.test.ts +++ b/public/app/features/dashboard/api/v1.test.ts @@ -8,7 +8,7 @@ import { K8sDashboardAPI } from './v1'; const mockDashboardDto: DashboardWithAccessInfo = { kind: 'DashboardWithAccessInfo', - apiVersion: 'v1alpha1', + apiVersion: 'v1beta1', metadata: { name: 'dash-uid', @@ -293,7 +293,7 @@ describe('v1 dashboard API', () => { await expect(api.getDashboardDTO('test')).rejects.toThrow('backend conversion not yet implemented'); }); - it.each(['v0alpha1', 'v1alpha1'])('should not throw for %s conversion errors', async (correctStoredVersion) => { + it.each(['v0alpha1', 'v1beta1'])('should not throw for %s conversion errors', async (correctStoredVersion) => { const mockDashboardWithError = { ...mockDashboardDto, status: { diff --git a/public/app/features/dashboard/api/v1.ts b/public/app/features/dashboard/api/v1.ts index 818b1b814a3..d02a1b06502 100644 --- a/public/app/features/dashboard/api/v1.ts +++ b/public/app/features/dashboard/api/v1.ts @@ -27,7 +27,7 @@ export class K8sDashboardAPI implements DashboardAPI { constructor() { this.client = new ScopedResourceClient({ group: 'dashboard.grafana.app', - version: 'v1alpha1', + version: 'v1beta1', resource: 'dashboards', }); } diff --git a/public/app/features/dashboard/api/v2.test.ts b/public/app/features/dashboard/api/v2.test.ts index 1c6be876e9a..a1bb6cfbe33 100644 --- a/public/app/features/dashboard/api/v2.test.ts +++ b/public/app/features/dashboard/api/v2.test.ts @@ -244,14 +244,14 @@ describe('v2 dashboard API', () => { await expect(api.getDashboardDTO('test')).rejects.toThrow('backend conversion not yet implemented'); }); - it('should throw DashboardVersionError for v1alpha1 conversion error', async () => { + it('should throw DashboardVersionError for v1beta1 conversion error', async () => { const mockDashboardWithError = { ...mockDashboardDto, status: { conversion: { failed: true, error: 'backend conversion not yet implemented', - storedVersion: 'v1alpha1', + storedVersion: 'v1beta1', }, }, }; diff --git a/public/app/features/dashboard/api/v2.ts b/public/app/features/dashboard/api/v2.ts index 59d3544c532..0f6d1486051 100644 --- a/public/app/features/dashboard/api/v2.ts +++ b/public/app/features/dashboard/api/v2.ts @@ -44,6 +44,7 @@ export class K8sDashboardV2API if ( dashboard.status?.conversion?.failed && (dashboard.status.conversion.storedVersion === 'v1alpha1' || + dashboard.status.conversion.storedVersion === 'v1beta1' || dashboard.status.conversion.storedVersion === 'v0alpha1') ) { throw new DashboardVersionError(dashboard.status.conversion.storedVersion, dashboard.status.conversion.error); From 9f07e49cdda78034a018afe973e09980d382433c Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Wed, 23 Apr 2025 16:14:09 -0400 Subject: [PATCH 071/146] Alerting: Add extended definition to prometheus alert rules api (#103320) * Alerting: Add extended definition to prometheus alert rules api This adds `isPaused` and `notificationSettings` to the paginated rules api to enable the paginated view of GMA rules. refactor: make alert rule status and state retrieval extensible This lets us get status from other sources than the local ruler. * update swagger spec * add safety checks in test --- .../ngalert/api/api_prometheus_test.go | 95 ++++++- .../ngalert/api/prometheus/api_prometheus.go | 231 ++++++++++-------- pkg/services/ngalert/api/tooling/api.json | 14 +- .../ngalert/api/tooling/definitions/prom.go | 8 +- pkg/services/ngalert/api/tooling/post.json | 15 +- pkg/services/ngalert/api/tooling/spec.json | 15 +- pkg/tests/api/alerting/api_prometheus_test.go | 7 + public/api-merged.json | 12 + public/openapi3.json | 12 + 9 files changed, 293 insertions(+), 116 deletions(-) diff --git a/pkg/services/ngalert/api/api_prometheus_test.go b/pkg/services/ngalert/api/api_prometheus_test.go index f1429bb01af..5ded45bef8a 100644 --- a/pkg/services/ngalert/api/api_prometheus_test.go +++ b/pkg/services/ngalert/api/api_prometheus_test.go @@ -349,7 +349,7 @@ func TestRouteGetRuleStatuses(t *testing.T) { t.Run("with a rule that only has one query", func(t *testing.T) { fakeStore, fakeAIM, api := setupAPI(t) - generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery()) + generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(), gen.WithNoNotificationSettings(), gen.WithIsPaused(false)) folder := fakeStore.Folders[orgID][0] r := api.RouteGetRuleStatuses(c) @@ -390,6 +390,7 @@ func TestRouteGetRuleStatuses(t *testing.T) { "__a_private_label_on_the_rule__": "a_value" }, "health": "ok", + "isPaused": false, "type": "alerting", "lastEvaluation": "2022-03-10T14:01:00Z", "duration": 180, @@ -411,9 +412,91 @@ func TestRouteGetRuleStatuses(t *testing.T) { `, folder.Fullpath), string(r.Body())) }) + t.Run("with a rule that is paused", func(t *testing.T) { + fakeStore, fakeAIM, api := setupAPI(t) + generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(), gen.WithNoNotificationSettings(), gen.WithIsPaused(true)) + folder := fakeStore.Folders[orgID][0] + + r := api.RouteGetRuleStatuses(c) + require.Equal(t, http.StatusOK, r.Status()) + require.JSONEq(t, fmt.Sprintf(` +{ + "status": "success", + "data": { + "groups": [{ + "name": "rule-group", + "file": "%s", + "folderUid": "namespaceUID", + "rules": [{ + "state": "inactive", + "name": "AlwaysFiring", + "folderUid": "namespaceUID", + "uid": "RuleUID", + "query": "vector(1)", + "queriedDatasourceUIDs": ["AUID"], + "alerts": [{ + "labels": { + "job": "prometheus" + }, + "annotations": { + "severity": "critical" + }, + "state": "Normal", + "activeAt": "0001-01-01T00:00:00Z", + "value": "" + }], + "totals": { + "normal": 1 + }, + "totalsFiltered": { + "normal": 1 + }, + "labels": { + "__a_private_label_on_the_rule__": "a_value" + }, + "health": "ok", + "isPaused": true, + "type": "alerting", + "lastEvaluation": "2022-03-10T14:01:00Z", + "duration": 180, + "keepFiringFor": 10, + "evaluationTime": 60 + }], + "totals": { + "inactive": 1 + }, + "interval": 60, + "lastEvaluation": "2022-03-10T14:01:00Z", + "evaluationTime": 60 + }], + "totals": { + "inactive": 1 + } + } +} +`, folder.Fullpath), string(r.Body())) + }) + + t.Run("with a rule that has notification settings", func(t *testing.T) { + fakeStore, fakeAIM, api := setupAPI(t) + notificationSettings := ngmodels.NotificationSettings{ + Receiver: "test-receiver", + GroupBy: []string{"job"}, + } + generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(), gen.WithNotificationSettings(notificationSettings), gen.WithIsPaused(false)) + r := api.RouteGetRuleStatuses(c) + require.Equal(t, http.StatusOK, r.Status()) + var res apimodels.RuleResponse + require.NoError(t, json.Unmarshal(r.Body(), &res)) + require.Len(t, res.Data.RuleGroups, 1) + require.Len(t, res.Data.RuleGroups[0].Rules, 1) + require.NotNil(t, res.Data.RuleGroups[0].Rules[0].NotificationSettings) + require.Equal(t, notificationSettings.Receiver, res.Data.RuleGroups[0].Rules[0].NotificationSettings.Receiver) + }) + t.Run("with the inclusion of internal Labels", func(t *testing.T) { fakeStore, fakeAIM, api := setupAPI(t) - generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery()) + generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(), gen.WithNoNotificationSettings(), gen.WithIsPaused(false)) folder := fakeStore.Folders[orgID][0] req, err := http.NewRequest("GET", "/api/v1/rules?includeInternalLabels=true", nil) @@ -461,6 +544,7 @@ func TestRouteGetRuleStatuses(t *testing.T) { "__alert_rule_uid__": "RuleUID" }, "health": "ok", + "isPaused": false, "type": "alerting", "lastEvaluation": "2022-03-10T14:01:00Z", "duration": 180, @@ -484,7 +568,7 @@ func TestRouteGetRuleStatuses(t *testing.T) { t.Run("with a rule that has multiple queries", func(t *testing.T) { fakeStore, fakeAIM, api := setupAPI(t) - generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withExpressionsMultiQuery()) + generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withExpressionsMultiQuery(), gen.WithNoNotificationSettings(), gen.WithIsPaused(false)) folder := fakeStore.Folders[orgID][0] r := api.RouteGetRuleStatuses(c) @@ -525,6 +609,7 @@ func TestRouteGetRuleStatuses(t *testing.T) { "__a_private_label_on_the_rule__": "a_value" }, "health": "ok", + "isPaused": false, "type": "alerting", "lastEvaluation": "2022-03-10T14:01:00Z", "duration": 180, @@ -1684,11 +1769,11 @@ func setupAPI(t *testing.T) (*fakes.RuleStore, *fakeAlertInstanceManager, Promet return fakeStore, fakeAIM, api } -func generateRuleAndInstanceWithQuery(t *testing.T, orgID int64, fakeAIM *fakeAlertInstanceManager, fakeStore *fakes.RuleStore, query ngmodels.AlertRuleMutator) { +func generateRuleAndInstanceWithQuery(t *testing.T, orgID int64, fakeAIM *fakeAlertInstanceManager, fakeStore *fakes.RuleStore, query ngmodels.AlertRuleMutator, additionalMutators ...ngmodels.AlertRuleMutator) { t.Helper() gen := ngmodels.RuleGen - r := gen.With(gen.WithOrgID(orgID), asFixture(), query).GenerateRef() + r := gen.With(append([]ngmodels.AlertRuleMutator{gen.WithOrgID(orgID), asFixture(), query}, additionalMutators...)...).GenerateRef() fakeAIM.GenerateAlertInstances(orgID, r.UID, 1, func(s *state.State) *state.State { s.Labels = data.Labels{ diff --git a/pkg/services/ngalert/api/prometheus/api_prometheus.go b/pkg/services/ngalert/api/prometheus/api_prometheus.go index 076fa35a171..99387809fa3 100644 --- a/pkg/services/ngalert/api/prometheus/api_prometheus.go +++ b/pkg/services/ngalert/api/prometheus/api_prometheus.go @@ -252,7 +252,7 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon namespaces[namespaceUID] = folder.Fullpath } - ruleResponse = PrepareRuleGroupStatuses(srv.log, srv.manager, srv.status, srv.store, RuleGroupStatusesOptions{ + ruleResponse = PrepareRuleGroupStatuses(srv.log, srv.store, RuleGroupStatusesOptions{ Ctx: c.Req.Context(), OrgID: c.OrgID, Query: c.Req.Form, @@ -260,12 +260,107 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon AuthorizeRuleGroup: func(rules []*ngmodels.AlertRule) (bool, error) { return srv.authz.HasAccessToRuleGroup(c.Req.Context(), c.SignedInUser, rules) }, - }) + }, RuleStatusMutatorGenerator(srv.status), RuleAlertStateMutatorGenerator(srv.manager)) return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse) } -func PrepareRuleGroupStatuses(log log.Logger, manager state.AlertInstanceManager, status StatusReader, store ListAlertRulesStore, opts RuleGroupStatusesOptions) apimodels.RuleResponse { +// mutator function used to attach status to the rule +type RuleStatusMutator func(source *ngmodels.AlertRule, toMutate *apimodels.AlertingRule) + +// mutator function used to attach alert states to the rule and returns the totals and filtered totals +type RuleAlertStateMutator func(source *ngmodels.AlertRule, toMutate *apimodels.AlertingRule, stateFilterSet map[eval.State]struct{}, matchers labels.Matchers, labelOptions []ngmodels.LabelOption) (total map[string]int64, filteredTotal map[string]int64) + +func RuleStatusMutatorGenerator(statusReader StatusReader) RuleStatusMutator { + return func(source *ngmodels.AlertRule, toMutate *apimodels.AlertingRule) { + status, ok := statusReader.Status(source.GetKey()) + // Grafana by design return "ok" health and default other fields for unscheduled rules. + // This differs from Prometheus. + if !ok { + status = ngmodels.RuleStatus{ + Health: "ok", + } + } + toMutate.Health = status.Health + toMutate.LastError = errorOrEmpty(status.LastError) + toMutate.LastEvaluation = status.EvaluationTimestamp + toMutate.EvaluationTime = status.EvaluationDuration.Seconds() + } +} + +func RuleAlertStateMutatorGenerator(manager state.AlertInstanceManager) RuleAlertStateMutator { + return func(source *ngmodels.AlertRule, toMutate *apimodels.AlertingRule, stateFilterSet map[eval.State]struct{}, matchers labels.Matchers, labelOptions []ngmodels.LabelOption) (map[string]int64, map[string]int64) { + states := manager.GetStatesForRuleUID(source.OrgID, source.UID) + totals := make(map[string]int64) + totalsFiltered := make(map[string]int64) + for _, alertState := range states { + activeAt := alertState.StartsAt + valString := "" + if alertState.State == eval.Alerting || alertState.State == eval.Pending || alertState.State == eval.Recovering { + valString = FormatValues(alertState) + } + stateKey := strings.ToLower(alertState.State.String()) + totals[stateKey] += 1 + // Do not add error twice when execution error state is Error + if alertState.Error != nil && source.ExecErrState != ngmodels.ErrorErrState { + totals["error"] += 1 + } + alert := apimodels.Alert{ + Labels: apimodels.LabelsFromMap(alertState.GetLabels(labelOptions...)), + Annotations: apimodels.LabelsFromMap(alertState.Annotations), + + // TODO: or should we make this two fields? Using one field lets the + // frontend use the same logic for parsing text on annotations and this. + State: state.FormatStateAndReason(alertState.State, alertState.StateReason), + ActiveAt: &activeAt, + Value: valString, + } + + // Set the state of the rule based on the state of its alerts. + // Only update the rule state with 'pending' or 'recovering' if the current state is 'inactive'. + // This prevents overwriting a higher-severity 'firing' state in the case of a rule with multiple alerts. + switch alertState.State { + case eval.Normal: + case eval.Pending: + if toMutate.State == "inactive" { + toMutate.State = "pending" + } + case eval.Recovering: + if toMutate.State == "inactive" { + toMutate.State = "recovering" + } + case eval.Alerting: + if toMutate.ActiveAt == nil || toMutate.ActiveAt.After(activeAt) { + toMutate.ActiveAt = &activeAt + } + toMutate.State = "firing" + case eval.Error: + case eval.NoData: + } + + if len(stateFilterSet) > 0 { + if _, ok := stateFilterSet[alertState.State]; !ok { + continue + } + } + + if !matchersMatch(matchers, alertState.Labels) { + continue + } + + totalsFiltered[stateKey] += 1 + // Do not add error twice when execution error state is Error + if alertState.Error != nil && source.ExecErrState != ngmodels.ErrorErrState { + totalsFiltered["error"] += 1 + } + + toMutate.Alerts = append(toMutate.Alerts, alert) + } + return totals, totalsFiltered + } +} + +func PrepareRuleGroupStatuses(log log.Logger, store ListAlertRulesStore, opts RuleGroupStatusesOptions, ruleStatusMutator RuleStatusMutator, alertStateMutator RuleAlertStateMutator) apimodels.RuleResponse { ruleResponse := apimodels.RuleResponse{ DiscoveryBase: apimodels.DiscoveryBase{ Status: "success", @@ -299,16 +394,16 @@ func PrepareRuleGroupStatuses(log log.Logger, manager state.AlertInstanceManager ruleResponse.ErrorType = apiv1.ErrBadData return ruleResponse } - withStates, err := getStatesFromQuery(opts.Query) + stateFilter, err := getStatesFromQuery(opts.Query) if err != nil { ruleResponse.Status = "error" ruleResponse.Error = err.Error() ruleResponse.ErrorType = apiv1.ErrBadData return ruleResponse } - withStatesFast := make(map[eval.State]struct{}) - for _, state := range withStates { - withStatesFast[state] = struct{}{} + stateFilterSet := make(map[eval.State]struct{}) + for _, state := range stateFilter { + stateFilterSet[state] = struct{}{} } var labelOptions []ngmodels.LabelOption @@ -392,14 +487,14 @@ func PrepareRuleGroupStatuses(log log.Logger, manager state.AlertInstanceManager break } - ruleGroup, totals := toRuleGroup(log, manager, status, rg.GroupKey, rg.Folder, rg.Rules, limitAlertsPerRule, withStatesFast, matchers, labelOptions) + ruleGroup, totals := toRuleGroup(log, rg.GroupKey, rg.Folder, rg.Rules, limitAlertsPerRule, stateFilterSet, matchers, labelOptions, ruleStatusMutator, alertStateMutator) ruleGroup.Totals = totals for k, v := range totals { rulesTotals[k] += v } - if len(withStates) > 0 { - filterRules(ruleGroup, withStatesFast) + if len(stateFilter) > 0 { + filterRules(ruleGroup, stateFilterSet) } if limitRulesPerGroup > -1 && int64(len(ruleGroup.Rules)) > limitRulesPerGroup { @@ -524,7 +619,7 @@ func matchersMatch(matchers []*labels.Matcher, labels map[string]string) bool { return true } -func toRuleGroup(log log.Logger, manager state.AlertInstanceManager, sr StatusReader, groupKey ngmodels.AlertRuleGroupKey, folderFullPath string, rules []*ngmodels.AlertRule, limitAlerts int64, withStates map[eval.State]struct{}, matchers labels.Matchers, labelOptions []ngmodels.LabelOption) (*apimodels.RuleGroup, map[string]int64) { +func toRuleGroup(log log.Logger, groupKey ngmodels.AlertRuleGroupKey, folderFullPath string, rules []*ngmodels.AlertRule, limitAlerts int64, stateFilterSet map[eval.State]struct{}, matchers labels.Matchers, labelOptions []ngmodels.LabelOption, ruleStatusMutator RuleStatusMutator, ruleAlertStateMutator RuleAlertStateMutator) (*apimodels.RuleGroup, map[string]int64) { newGroup := &apimodels.RuleGroup{ Name: groupKey.RuleGroup, // file is what Prometheus uses for provisioning, we replace it with namespace which is the folder in Grafana. @@ -536,112 +631,39 @@ func toRuleGroup(log log.Logger, manager state.AlertInstanceManager, sr StatusRe ngmodels.RulesGroup(rules).SortByGroupIndex() for _, rule := range rules { - status, ok := sr.Status(rule.GetKey()) - // Grafana by design return "ok" health and default other fields for unscheduled rules. - // This differs from Prometheus. - if !ok { - status = ngmodels.RuleStatus{ - Health: "ok", - } - } - - queriedDatasourceUIDs := extractDatasourceUIDs(rule) - alertingRule := apimodels.AlertingRule{ State: "inactive", Name: rule.Title, Query: ruleToQuery(log, rule), - QueriedDatasourceUIDs: queriedDatasourceUIDs, + QueriedDatasourceUIDs: extractDatasourceUIDs(rule), Duration: rule.For.Seconds(), KeepFiringFor: rule.KeepFiringFor.Seconds(), Annotations: apimodels.LabelsFromMap(rule.Annotations), + Rule: apimodels.Rule{ + UID: rule.UID, + Name: rule.Title, + FolderUID: rule.NamespaceUID, + Labels: apimodels.LabelsFromMap(rule.GetLabels(labelOptions...)), + Type: rule.Type().String(), + IsPaused: rule.IsPaused, + }, } - newRule := apimodels.Rule{ - UID: rule.UID, - Name: rule.Title, - FolderUID: rule.NamespaceUID, - Labels: apimodels.LabelsFromMap(rule.GetLabels(labelOptions...)), - Health: status.Health, - LastError: errorOrEmpty(status.LastError), - Type: rule.Type().String(), - LastEvaluation: status.EvaluationTimestamp, - EvaluationTime: status.EvaluationDuration.Seconds(), - } - - states := manager.GetStatesForRuleUID(rule.OrgID, rule.UID) - totals := make(map[string]int64) - totalsFiltered := make(map[string]int64) - for _, alertState := range states { - activeAt := alertState.StartsAt - valString := "" - if alertState.State == eval.Alerting || alertState.State == eval.Pending || alertState.State == eval.Recovering { - valString = FormatValues(alertState) - } - stateKey := strings.ToLower(alertState.State.String()) - totals[stateKey] += 1 - // Do not add error twice when execution error state is Error - if alertState.Error != nil && rule.ExecErrState != ngmodels.ErrorErrState { - totals["error"] += 1 - } - alert := apimodels.Alert{ - Labels: apimodels.LabelsFromMap(alertState.GetLabels(labelOptions...)), - Annotations: apimodels.LabelsFromMap(alertState.Annotations), - - // TODO: or should we make this two fields? Using one field lets the - // frontend use the same logic for parsing text on annotations and this. - State: state.FormatStateAndReason(alertState.State, alertState.StateReason), - ActiveAt: &activeAt, - Value: valString, - } - - // Set the state of the rule based on the state of its alerts. - // Only update the rule state with 'pending' or 'recovering' if the current state is 'inactive'. - // This prevents overwriting a higher-severity 'firing' state in the case of a rule with multiple alerts. - switch alertState.State { - case eval.Normal: - case eval.Pending: - if alertingRule.State == "inactive" { - alertingRule.State = "pending" - } - case eval.Recovering: - if alertingRule.State == "inactive" { - alertingRule.State = "recovering" - } - case eval.Alerting: - if alertingRule.ActiveAt == nil || alertingRule.ActiveAt.After(activeAt) { - alertingRule.ActiveAt = &activeAt - } - alertingRule.State = "firing" - case eval.Error: - case eval.NoData: - } - - if len(withStates) > 0 { - if _, ok := withStates[alertState.State]; !ok { - continue - } - } - - if !matchersMatch(matchers, alertState.Labels) { - continue - } - - totalsFiltered[stateKey] += 1 - // Do not add error twice when execution error state is Error - if alertState.Error != nil && rule.ExecErrState != ngmodels.ErrorErrState { - totalsFiltered["error"] += 1 - } - - alertingRule.Alerts = append(alertingRule.Alerts, alert) + // mutate rule to apply status fields + ruleStatusMutator(rule, &alertingRule) + + if len(rule.NotificationSettings) > 0 { + alertingRule.NotificationSettings = (*apimodels.AlertRuleNotificationSettings)(&rule.NotificationSettings[0]) } + // mutate rule for alert states + totals, totalsFiltered := ruleAlertStateMutator(rule, &alertingRule, stateFilterSet, matchers, labelOptions) if alertingRule.State != "" { rulesTotals[alertingRule.State] += 1 } - if newRule.Health == "error" || newRule.Health == "nodata" { - rulesTotals[newRule.Health] += 1 + if alertingRule.Health == "error" || alertingRule.Health == "nodata" { + rulesTotals[alertingRule.Health] += 1 } alertsBy := apimodels.AlertsBy(apimodels.AlertsByImportance) @@ -654,14 +676,13 @@ func toRuleGroup(log log.Logger, manager state.AlertInstanceManager, sr StatusRe alertsBy.Sort(alertingRule.Alerts) } - alertingRule.Rule = newRule alertingRule.Totals = totals alertingRule.TotalsFiltered = totalsFiltered newGroup.Rules = append(newGroup.Rules, alertingRule) newGroup.Interval = float64(rule.IntervalSeconds) // TODO yuri. Change that when scheduler will process alerts in groups - newGroup.EvaluationTime = newRule.EvaluationTime - newGroup.LastEvaluation = newRule.LastEvaluation + newGroup.EvaluationTime = alertingRule.EvaluationTime + newGroup.LastEvaluation = alertingRule.LastEvaluation } return newGroup, rulesTotals diff --git a/pkg/services/ngalert/api/tooling/api.json b/pkg/services/ngalert/api/tooling/api.json index e6305fc2c91..9a221175f58 100644 --- a/pkg/services/ngalert/api/tooling/api.json +++ b/pkg/services/ngalert/api/tooling/api.json @@ -475,6 +475,9 @@ "health": { "type": "string" }, + "isPaused": { + "type": "boolean" + }, "keepFiringFor": { "format": "double", "type": "number" @@ -492,6 +495,9 @@ "name": { "type": "string" }, + "notificationSettings": { + "$ref": "#/definitions/AlertRuleNotificationSettings" + }, "queriedDatasourceUIDs": { "items": { "type": "string" @@ -3769,6 +3775,9 @@ "health": { "type": "string" }, + "isPaused": { + "type": "boolean" + }, "labels": { "$ref": "#/definitions/Labels" }, @@ -3782,6 +3791,9 @@ "name": { "type": "string" }, + "notificationSettings": { + "$ref": "#/definitions/AlertRuleNotificationSettings" + }, "query": { "type": "string" }, @@ -6649,4 +6661,4 @@ } }, "swagger": "2.0" -} \ No newline at end of file +} diff --git a/pkg/services/ngalert/api/tooling/definitions/prom.go b/pkg/services/ngalert/api/tooling/definitions/prom.go index 20130c9f331..48a96f99c80 100644 --- a/pkg/services/ngalert/api/tooling/definitions/prom.go +++ b/pkg/services/ngalert/api/tooling/definitions/prom.go @@ -180,9 +180,11 @@ type Rule struct { Health string `json:"health"` LastError string `json:"lastError,omitempty"` // required: true - Type string `json:"type"` - LastEvaluation time.Time `json:"lastEvaluation"` - EvaluationTime float64 `json:"evaluationTime"` + Type string `json:"type"` + LastEvaluation time.Time `json:"lastEvaluation"` + EvaluationTime float64 `json:"evaluationTime"` + IsPaused bool `json:"isPaused"` + NotificationSettings *AlertRuleNotificationSettings `json:"notificationSettings,omitempty"` } // Alert has info for an alert. diff --git a/pkg/services/ngalert/api/tooling/post.json b/pkg/services/ngalert/api/tooling/post.json index 6f005554994..3a75aca4379 100644 --- a/pkg/services/ngalert/api/tooling/post.json +++ b/pkg/services/ngalert/api/tooling/post.json @@ -475,6 +475,9 @@ "health": { "type": "string" }, + "isPaused": { + "type": "boolean" + }, "keepFiringFor": { "format": "double", "type": "number" @@ -492,6 +495,9 @@ "name": { "type": "string" }, + "notificationSettings": { + "$ref": "#/definitions/AlertRuleNotificationSettings" + }, "queriedDatasourceUIDs": { "items": { "type": "string" @@ -3769,6 +3775,9 @@ "health": { "type": "string" }, + "isPaused": { + "type": "boolean" + }, "labels": { "$ref": "#/definitions/Labels" }, @@ -3782,6 +3791,9 @@ "name": { "type": "string" }, + "notificationSettings": { + "$ref": "#/definitions/AlertRuleNotificationSettings" + }, "query": { "type": "string" }, @@ -4876,6 +4888,7 @@ "type": "object" }, "alertGroups": { + "description": "AlertGroups alert groups", "items": { "$ref": "#/definitions/alertGroup", "type": "object" @@ -9520,4 +9533,4 @@ } }, "swagger": "2.0" -} \ No newline at end of file +} diff --git a/pkg/services/ngalert/api/tooling/spec.json b/pkg/services/ngalert/api/tooling/spec.json index bc9e62927f7..8996ff3e13f 100644 --- a/pkg/services/ngalert/api/tooling/spec.json +++ b/pkg/services/ngalert/api/tooling/spec.json @@ -4583,6 +4583,9 @@ "health": { "type": "string" }, + "isPaused": { + "type": "boolean" + }, "keepFiringFor": { "type": "number", "format": "double" @@ -4600,6 +4603,9 @@ "name": { "type": "string" }, + "notificationSettings": { + "$ref": "#/definitions/AlertRuleNotificationSettings" + }, "queriedDatasourceUIDs": { "type": "array", "items": { @@ -7876,6 +7882,9 @@ "health": { "type": "string" }, + "isPaused": { + "type": "boolean" + }, "labels": { "$ref": "#/definitions/Labels" }, @@ -7889,6 +7898,9 @@ "name": { "type": "string" }, + "notificationSettings": { + "$ref": "#/definitions/AlertRuleNotificationSettings" + }, "query": { "type": "string" }, @@ -8976,6 +8988,7 @@ } }, "alertGroups": { + "description": "AlertGroups alert groups", "type": "array", "items": { "type": "object", @@ -9553,4 +9566,4 @@ "type": "basic" } } -} \ No newline at end of file +} diff --git a/pkg/tests/api/alerting/api_prometheus_test.go b/pkg/tests/api/alerting/api_prometheus_test.go index 1114d1102b2..3bf3e889ce1 100644 --- a/pkg/tests/api/alerting/api_prometheus_test.go +++ b/pkg/tests/api/alerting/api_prometheus_test.go @@ -260,6 +260,7 @@ func TestIntegrationPrometheusRules(t *testing.T) { "label1": "val1" }, "health": "ok", + "isPaused": false, "type": "alerting", "lastEvaluation": "0001-01-01T00:00:00Z", "evaluationTime": 0 @@ -270,6 +271,7 @@ func TestIntegrationPrometheusRules(t *testing.T) { "folderUid": "default", "uid": "%s", "health": "ok", + "isPaused": false, "type": "alerting", "lastEvaluation": "0001-01-01T00:00:00Z", "evaluationTime": 0 @@ -323,6 +325,7 @@ func TestIntegrationPrometheusRules(t *testing.T) { "label1": "val1" }, "health": "ok", + "isPaused": false, "type": "alerting", "lastEvaluation": "0001-01-01T00:00:00Z", "evaluationTime": 0 @@ -333,6 +336,7 @@ func TestIntegrationPrometheusRules(t *testing.T) { "folderUid": "default", "uid": "%s", "health": "ok", + "isPaused": false, "type": "alerting", "lastEvaluation": "0001-01-01T00:00:00Z", "evaluationTime": 0 @@ -483,6 +487,7 @@ func TestIntegrationPrometheusRulesFilterByDashboard(t *testing.T) { "__panelId__": "1" }, "health": "ok", + "isPaused": false, "type": "alerting", "lastEvaluation": "0001-01-01T00:00:00Z", "evaluationTime": 0 @@ -493,6 +498,7 @@ func TestIntegrationPrometheusRulesFilterByDashboard(t *testing.T) { "folderUid": "default", "query": "[{\"refId\":\"A\",\"queryType\":\"\",\"relativeTimeRange\":{\"from\":18000,\"to\":10800},\"datasourceUid\":\"__expr__\",\"model\":{\"expression\":\"2 + 3 \\u003e 1\",\"intervalMs\":1000,\"maxDataPoints\":43200,\"type\":\"math\"}}]", "health": "ok", + "isPaused": false, "type": "alerting", "lastEvaluation": "0001-01-01T00:00:00Z", "evaluationTime": 0 @@ -530,6 +536,7 @@ func TestIntegrationPrometheusRulesFilterByDashboard(t *testing.T) { "__panelId__": "1" }, "health": "ok", + "isPaused": false, "type": "alerting", "lastEvaluation": "0001-01-01T00:00:00Z", "evaluationTime": 0 diff --git a/public/api-merged.json b/public/api-merged.json index aa6563b5c1c..fce0085ad8b 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -12894,6 +12894,9 @@ "health": { "type": "string" }, + "isPaused": { + "type": "boolean" + }, "keepFiringFor": { "type": "number", "format": "double" @@ -12911,6 +12914,9 @@ "name": { "type": "string" }, + "notificationSettings": { + "$ref": "#/definitions/AlertRuleNotificationSettings" + }, "queriedDatasourceUIDs": { "type": "array", "items": { @@ -20152,6 +20158,9 @@ "health": { "type": "string" }, + "isPaused": { + "type": "boolean" + }, "labels": { "$ref": "#/definitions/Labels" }, @@ -20165,6 +20174,9 @@ "name": { "type": "string" }, + "notificationSettings": { + "$ref": "#/definitions/AlertRuleNotificationSettings" + }, "query": { "type": "string" }, diff --git a/public/openapi3.json b/public/openapi3.json index fe5310802bb..081b3a7445f 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -2946,6 +2946,9 @@ "health": { "type": "string" }, + "isPaused": { + "type": "boolean" + }, "keepFiringFor": { "format": "double", "type": "number" @@ -2963,6 +2966,9 @@ "name": { "type": "string" }, + "notificationSettings": { + "$ref": "#/components/schemas/AlertRuleNotificationSettings" + }, "queriedDatasourceUIDs": { "items": { "type": "string" @@ -10207,6 +10213,9 @@ "health": { "type": "string" }, + "isPaused": { + "type": "boolean" + }, "labels": { "$ref": "#/components/schemas/Labels" }, @@ -10220,6 +10229,9 @@ "name": { "type": "string" }, + "notificationSettings": { + "$ref": "#/components/schemas/AlertRuleNotificationSettings" + }, "query": { "type": "string" }, From 8a5b77432c648f49f1874641f1bce111c9bcf3e5 Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Thu, 24 Apr 2025 09:06:16 +0300 Subject: [PATCH 072/146] Grafana-icons: Remove package (#104290) * Remove grafana-icons package * Cleanup --- package.json | 3 - packages/grafana-icons/.gitignore | 7 - packages/grafana-icons/.svgrrc.cjs | 32 -- packages/grafana-icons/CHANGELOG.md | 0 packages/grafana-icons/LICENSE_APACHE2 | 202 ------------ packages/grafana-icons/README.md | 14 - packages/grafana-icons/package.json | 68 ----- packages/grafana-icons/project.json | 11 - packages/grafana-icons/rollup.config.ts | 15 - packages/grafana-icons/src/IconBase.tsx | 50 --- packages/grafana-icons/svg/adjust-circle.svg | 1 - packages/grafana-icons/svg/ai.svg | 5 - packages/grafana-icons/svg/align-left.svg | 1 - packages/grafana-icons/svg/align-right.svg | 1 - packages/grafana-icons/svg/amazon.svg | 1 - packages/grafana-icons/svg/anchor.svg | 1 - .../grafana-icons/svg/angle-double-down.svg | 1 - .../grafana-icons/svg/angle-double-right.svg | 1 - .../grafana-icons/svg/angle-double-up.svg | 1 - packages/grafana-icons/svg/angle-down.svg | 1 - packages/grafana-icons/svg/angle-left.svg | 1 - packages/grafana-icons/svg/angle-right.svg | 1 - packages/grafana-icons/svg/angle-up.svg | 1 - .../svg/application-observability.svg | 9 - packages/grafana-icons/svg/apps.svg | 1 - packages/grafana-icons/svg/archive-alt.svg | 1 - packages/grafana-icons/svg/arrow-down.svg | 1 - .../grafana-icons/svg/arrow-from-right.svg | 1 - packages/grafana-icons/svg/arrow-left.svg | 1 - packages/grafana-icons/svg/arrow-random.svg | 1 - packages/grafana-icons/svg/arrow-right.svg | 1 - packages/grafana-icons/svg/arrow-to-right.svg | 1 - packages/grafana-icons/svg/arrow-up.svg | 1 - packages/grafana-icons/svg/arrow.svg | 1 - packages/grafana-icons/svg/arrows-h.svg | 1 - packages/grafana-icons/svg/arrows-v.svg | 1 - packages/grafana-icons/svg/asserts.svg | 3 - packages/grafana-icons/svg/at.svg | 1 - packages/grafana-icons/svg/backward.svg | 1 - .../grafana-icons/svg/bar-alignment-after.svg | 7 - .../svg/bar-alignment-before.svg | 7 - .../svg/bar-alignment-center.svg | 7 - packages/grafana-icons/svg/bars.svg | 1 - packages/grafana-icons/svg/bell-slash.svg | 1 - packages/grafana-icons/svg/bell.svg | 1 - packages/grafana-icons/svg/bolt.svg | 1 - packages/grafana-icons/svg/book-open.svg | 1 - packages/grafana-icons/svg/book.svg | 1 - packages/grafana-icons/svg/bookmark.svg | 1 - packages/grafana-icons/svg/brackets-curly.svg | 1 - packages/grafana-icons/svg/bug.svg | 1 - packages/grafana-icons/svg/building.svg | 1 - packages/grafana-icons/svg/calculator-alt.svg | 1 - packages/grafana-icons/svg/calendar-alt.svg | 1 - packages/grafana-icons/svg/calendar-slash.svg | 1 - packages/grafana-icons/svg/camera.svg | 1 - packages/grafana-icons/svg/cancel.svg | 1 - packages/grafana-icons/svg/capture.svg | 1 - packages/grafana-icons/svg/channel-add.svg | 1 - packages/grafana-icons/svg/chart-line.svg | 1 - packages/grafana-icons/svg/check-circle.svg | 1 - packages/grafana-icons/svg/check-square.svg | 1 - packages/grafana-icons/svg/check.svg | 1 - packages/grafana-icons/svg/circle-mono.svg | 1 - packages/grafana-icons/svg/circle.svg | 1 - packages/grafana-icons/svg/clipboard-alt.svg | 1 - packages/grafana-icons/svg/clock-nine.svg | 1 - packages/grafana-icons/svg/cloud-download.svg | 1 - packages/grafana-icons/svg/cloud-upload.svg | 1 - packages/grafana-icons/svg/cloud.svg | 1 - packages/grafana-icons/svg/code-branch.svg | 1 - packages/grafana-icons/svg/cog.svg | 1 - packages/grafana-icons/svg/columns.svg | 1 - .../grafana-icons/svg/comment-alt-message.svg | 1 - .../grafana-icons/svg/comment-alt-share.svg | 1 - packages/grafana-icons/svg/comment-alt.svg | 1 - packages/grafana-icons/svg/comments-alt.svg | 1 - packages/grafana-icons/svg/compass.svg | 1 - packages/grafana-icons/svg/copy.svg | 1 - .../svg/corner-down-right-alt.svg | 1 - .../grafana-icons/svg/create-dashboard.svg | 1 - packages/grafana-icons/svg/credit-card.svg | 1 - packages/grafana-icons/svg/crosshair.svg | 1 - packages/grafana-icons/svg/cube.svg | 1 - packages/grafana-icons/svg/dashboard.svg | 1 - packages/grafana-icons/svg/database.svg | 1 - packages/grafana-icons/svg/dice-three.svg | 1 - packages/grafana-icons/svg/discord.svg | 1 - packages/grafana-icons/svg/docker.svg | 1 - packages/grafana-icons/svg/document-info.svg | 1 - .../svg/document-layout-left.svg | 1 - packages/grafana-icons/svg/download-alt.svg | 1 - packages/grafana-icons/svg/draggabledots.svg | 1 - packages/grafana-icons/svg/drilldown.svg | 6 - packages/grafana-icons/svg/edit.svg | 1 - packages/grafana-icons/svg/ellipsis-h.svg | 1 - packages/grafana-icons/svg/ellipsis-v.svg | 1 - packages/grafana-icons/svg/enter.svg | 1 - packages/grafana-icons/svg/envelope.svg | 1 - packages/grafana-icons/svg/exchange-alt.svg | 1 - .../grafana-icons/svg/exclamation-circle.svg | 1 - .../svg/exclamation-triangle.svg | 1 - packages/grafana-icons/svg/expand-arrows.svg | 1 - .../grafana-icons/svg/external-link-alt.svg | 1 - packages/grafana-icons/svg/eye-slash.svg | 1 - packages/grafana-icons/svg/eye.svg | 1 - packages/grafana-icons/svg/favorite.svg | 1 - packages/grafana-icons/svg/file-alt.svg | 1 - packages/grafana-icons/svg/file-blank.svg | 1 - packages/grafana-icons/svg/file-copy-alt.svg | 1 - packages/grafana-icons/svg/file-download.svg | 1 - packages/grafana-icons/svg/file-edit-alt.svg | 1 - .../grafana-icons/svg/file-landscape-alt.svg | 1 - packages/grafana-icons/svg/filter.svg | 1 - packages/grafana-icons/svg/fire.svg | 1 - packages/grafana-icons/svg/flip.svg | 4 - packages/grafana-icons/svg/folder-open.svg | 1 - packages/grafana-icons/svg/folder-plus.svg | 1 - packages/grafana-icons/svg/folder-upload.svg | 1 - packages/grafana-icons/svg/folder.svg | 1 - packages/grafana-icons/svg/font.svg | 1 - packages/grafana-icons/svg/forward.svg | 1 - .../svg/frontend-observability.svg | 6 - packages/grafana-icons/svg/github.svg | 1 - packages/grafana-icons/svg/gitlab.svg | 1 - packages/grafana-icons/svg/globe.svg | 1 - packages/grafana-icons/svg/glue.svg | 3 - .../grafana-icons/svg/google-hangouts-alt.svg | 1 - packages/grafana-icons/svg/google.svg | 1 - packages/grafana-icons/svg/grafana.svg | 1 - packages/grafana-icons/svg/graph-bar.svg | 1 - packages/grafana-icons/svg/grid.svg | 7 - packages/grafana-icons/svg/heart-break.svg | 1 - packages/grafana-icons/svg/heart-rate.svg | 1 - packages/grafana-icons/svg/heart.svg | 1 - packages/grafana-icons/svg/hipchat.svg | 1 - packages/grafana-icons/svg/history-alt.svg | 1 - packages/grafana-icons/svg/history.svg | 1 - packages/grafana-icons/svg/home-alt.svg | 1 - packages/grafana-icons/svg/home.svg | 1 - .../svg/horizontal-align-center.svg | 1 - .../svg/horizontal-align-left.svg | 1 - .../svg/horizontal-align-right.svg | 1 - packages/grafana-icons/svg/hourglass.svg | 1 - packages/grafana-icons/svg/import.svg | 1 - packages/grafana-icons/svg/info-circle.svg | 1 - packages/grafana-icons/svg/info.svg | 1 - .../svg/interpolation-linear.svg | 7 - .../svg/interpolation-smooth.svg | 7 - .../svg/interpolation-step-after.svg | 7 - .../svg/interpolation-step-before.svg | 7 - packages/grafana-icons/svg/k6.svg | 5 - .../grafana-icons/svg/key-skeleton-alt.svg | 1 - packages/grafana-icons/svg/keyboard.svg | 1 - packages/grafana-icons/svg/landscape.svg | 7 - packages/grafana-icons/svg/layer-group.svg | 1 - packages/grafana-icons/svg/layers-alt.svg | 1 - packages/grafana-icons/svg/layout-simple.svg | 6 - packages/grafana-icons/svg/library-panel.svg | 1 - packages/grafana-icons/svg/line-alt.svg | 1 - packages/grafana-icons/svg/line.svg | 1 - packages/grafana-icons/svg/link.svg | 1 - packages/grafana-icons/svg/list-ol.svg | 1 - packages/grafana-icons/svg/list-ui-alt.svg | 1 - packages/grafana-icons/svg/list-ul.svg | 1 - packages/grafana-icons/svg/lock.svg | 1 - packages/grafana-icons/svg/logs.svg | 3 - .../grafana-icons/svg/map-marker-minus.svg | 1 - .../grafana-icons/svg/map-marker-plus.svg | 1 - packages/grafana-icons/svg/map-marker.svg | 1 - packages/grafana-icons/svg/message.svg | 1 - packages/grafana-icons/svg/microsoft.svg | 1 - packages/grafana-icons/svg/minus-circle.svg | 1 - packages/grafana-icons/svg/minus.svg | 1 - packages/grafana-icons/svg/ml.svg | 1 - packages/grafana-icons/svg/mobile-android.svg | 1 - packages/grafana-icons/svg/monitor.svg | 1 - packages/grafana-icons/svg/movepane-left.svg | 1 - packages/grafana-icons/svg/movepane-right.svg | 1 - packages/grafana-icons/svg/okta.svg | 1 - packages/grafana-icons/svg/pagerduty.svg | 1 - packages/grafana-icons/svg/palette.svg | 1 - packages/grafana-icons/svg/panel-add.svg | 1 - packages/grafana-icons/svg/paragraph.svg | 1 - .../grafana-icons/svg/pathfinder-unite.svg | 1 - packages/grafana-icons/svg/pause.svg | 1 - packages/grafana-icons/svg/pen.svg | 1 - packages/grafana-icons/svg/percentage.svg | 1 - packages/grafana-icons/svg/pin.svg | 4 - packages/grafana-icons/svg/play.svg | 1 - packages/grafana-icons/svg/plug.svg | 1 - packages/grafana-icons/svg/plus-circle.svg | 1 - packages/grafana-icons/svg/plus-square.svg | 1 - packages/grafana-icons/svg/plus.svg | 1 - packages/grafana-icons/svg/portrait.svg | 7 - packages/grafana-icons/svg/power.svg | 1 - .../grafana-icons/svg/presentation-play.svg | 1 - packages/grafana-icons/svg/process.svg | 1 - packages/grafana-icons/svg/prometheus.svg | 3 - .../grafana-icons/svg/question-circle.svg | 1 - packages/grafana-icons/svg/record-audio.svg | 1 - packages/grafana-icons/svg/repeat.svg | 1 - packages/grafana-icons/svg/rocket.svg | 1 - packages/grafana-icons/svg/rss.svg | 1 - packages/grafana-icons/svg/ruler-combined.svg | 1 - packages/grafana-icons/svg/save.svg | 1 - packages/grafana-icons/svg/search-minus.svg | 1 - packages/grafana-icons/svg/search-plus.svg | 1 - packages/grafana-icons/svg/search.svg | 1 - .../grafana-icons/svg/service-account.svg | 5 - packages/grafana-icons/svg/share-alt.svg | 1 - .../grafana-icons/svg/shield-exclamation.svg | 1 - packages/grafana-icons/svg/shield.svg | 1 - packages/grafana-icons/svg/show-context.svg | 6 - packages/grafana-icons/svg/signal.svg | 1 - packages/grafana-icons/svg/signin.svg | 1 - packages/grafana-icons/svg/signout.svg | 1 - packages/grafana-icons/svg/sitemap.svg | 1 - packages/grafana-icons/svg/slack.svg | 1 - packages/grafana-icons/svg/sliders-v-alt.svg | 1 - .../grafana-icons/svg/sort-amount-down.svg | 1 - packages/grafana-icons/svg/sort-amount-up.svg | 1 - packages/grafana-icons/svg/spinner.svg | 3 - packages/grafana-icons/svg/square-shape.svg | 1 - packages/grafana-icons/svg/star.svg | 1 - packages/grafana-icons/svg/step-backward.svg | 1 - .../grafana-icons/svg/stopwatch-slash.svg | 1 - packages/grafana-icons/svg/stopwatch.svg | 1 - packages/grafana-icons/svg/sync-slash.svg | 1 - packages/grafana-icons/svg/sync.svg | 1 - .../grafana-icons/svg/table-collapse-all.svg | 7 - .../grafana-icons/svg/table-expand-all.svg | 7 - packages/grafana-icons/svg/table.svg | 1 - packages/grafana-icons/svg/tag-alt.svg | 1 - packages/grafana-icons/svg/telegram-alt.svg | 1 - packages/grafana-icons/svg/text-fields.svg | 1 - packages/grafana-icons/svg/thumbs-up.svg | 1 - packages/grafana-icons/svg/times-circle.svg | 1 - packages/grafana-icons/svg/times.svg | 1 - packages/grafana-icons/svg/toggle-off.svg | 1 - packages/grafana-icons/svg/toggle-on.svg | 1 - packages/grafana-icons/svg/traces.svg | 7 - packages/grafana-icons/svg/trash-alt.svg | 1 - packages/grafana-icons/svg/unarchive.svg | 4 - packages/grafana-icons/svg/unlock.svg | 1 - packages/grafana-icons/svg/upload.svg | 1 - packages/grafana-icons/svg/user-arrows.svg | 1 - packages/grafana-icons/svg/user.svg | 1 - packages/grafana-icons/svg/users-alt.svg | 1 - .../svg/vertical-align-bottom.svg | 1 - .../svg/vertical-align-center.svg | 1 - .../grafana-icons/svg/vertical-align-top.svg | 1 - .../grafana-icons/svg/web-section-alt.svg | 1 - packages/grafana-icons/svg/wrap-text.svg | 1 - packages/grafana-icons/svg/x.svg | 1 - packages/grafana-icons/templates/icon.cjs | 46 --- packages/grafana-icons/templates/index.cjs | 12 - packages/grafana-icons/tsconfig.build.json | 4 - packages/grafana-icons/tsconfig.json | 14 - project.json | 2 - yarn.lock | 287 +----------------- 261 files changed, 8 insertions(+), 1141 deletions(-) delete mode 100644 packages/grafana-icons/.gitignore delete mode 100644 packages/grafana-icons/.svgrrc.cjs delete mode 100644 packages/grafana-icons/CHANGELOG.md delete mode 100644 packages/grafana-icons/LICENSE_APACHE2 delete mode 100644 packages/grafana-icons/README.md delete mode 100644 packages/grafana-icons/package.json delete mode 100644 packages/grafana-icons/project.json delete mode 100644 packages/grafana-icons/rollup.config.ts delete mode 100644 packages/grafana-icons/src/IconBase.tsx delete mode 100644 packages/grafana-icons/svg/adjust-circle.svg delete mode 100644 packages/grafana-icons/svg/ai.svg delete mode 100644 packages/grafana-icons/svg/align-left.svg delete mode 100644 packages/grafana-icons/svg/align-right.svg delete mode 100644 packages/grafana-icons/svg/amazon.svg delete mode 100644 packages/grafana-icons/svg/anchor.svg delete mode 100644 packages/grafana-icons/svg/angle-double-down.svg delete mode 100644 packages/grafana-icons/svg/angle-double-right.svg delete mode 100644 packages/grafana-icons/svg/angle-double-up.svg delete mode 100644 packages/grafana-icons/svg/angle-down.svg delete mode 100644 packages/grafana-icons/svg/angle-left.svg delete mode 100644 packages/grafana-icons/svg/angle-right.svg delete mode 100644 packages/grafana-icons/svg/angle-up.svg delete mode 100644 packages/grafana-icons/svg/application-observability.svg delete mode 100644 packages/grafana-icons/svg/apps.svg delete mode 100644 packages/grafana-icons/svg/archive-alt.svg delete mode 100644 packages/grafana-icons/svg/arrow-down.svg delete mode 100644 packages/grafana-icons/svg/arrow-from-right.svg delete mode 100644 packages/grafana-icons/svg/arrow-left.svg delete mode 100644 packages/grafana-icons/svg/arrow-random.svg delete mode 100644 packages/grafana-icons/svg/arrow-right.svg delete mode 100644 packages/grafana-icons/svg/arrow-to-right.svg delete mode 100644 packages/grafana-icons/svg/arrow-up.svg delete mode 100644 packages/grafana-icons/svg/arrow.svg delete mode 100644 packages/grafana-icons/svg/arrows-h.svg delete mode 100644 packages/grafana-icons/svg/arrows-v.svg delete mode 100644 packages/grafana-icons/svg/asserts.svg delete mode 100644 packages/grafana-icons/svg/at.svg delete mode 100644 packages/grafana-icons/svg/backward.svg delete mode 100644 packages/grafana-icons/svg/bar-alignment-after.svg delete mode 100644 packages/grafana-icons/svg/bar-alignment-before.svg delete mode 100644 packages/grafana-icons/svg/bar-alignment-center.svg delete mode 100644 packages/grafana-icons/svg/bars.svg delete mode 100644 packages/grafana-icons/svg/bell-slash.svg delete mode 100644 packages/grafana-icons/svg/bell.svg delete mode 100644 packages/grafana-icons/svg/bolt.svg delete mode 100644 packages/grafana-icons/svg/book-open.svg delete mode 100644 packages/grafana-icons/svg/book.svg delete mode 100644 packages/grafana-icons/svg/bookmark.svg delete mode 100644 packages/grafana-icons/svg/brackets-curly.svg delete mode 100644 packages/grafana-icons/svg/bug.svg delete mode 100644 packages/grafana-icons/svg/building.svg delete mode 100644 packages/grafana-icons/svg/calculator-alt.svg delete mode 100644 packages/grafana-icons/svg/calendar-alt.svg delete mode 100644 packages/grafana-icons/svg/calendar-slash.svg delete mode 100644 packages/grafana-icons/svg/camera.svg delete mode 100644 packages/grafana-icons/svg/cancel.svg delete mode 100644 packages/grafana-icons/svg/capture.svg delete mode 100644 packages/grafana-icons/svg/channel-add.svg delete mode 100644 packages/grafana-icons/svg/chart-line.svg delete mode 100644 packages/grafana-icons/svg/check-circle.svg delete mode 100644 packages/grafana-icons/svg/check-square.svg delete mode 100644 packages/grafana-icons/svg/check.svg delete mode 100644 packages/grafana-icons/svg/circle-mono.svg delete mode 100644 packages/grafana-icons/svg/circle.svg delete mode 100644 packages/grafana-icons/svg/clipboard-alt.svg delete mode 100644 packages/grafana-icons/svg/clock-nine.svg delete mode 100644 packages/grafana-icons/svg/cloud-download.svg delete mode 100644 packages/grafana-icons/svg/cloud-upload.svg delete mode 100644 packages/grafana-icons/svg/cloud.svg delete mode 100644 packages/grafana-icons/svg/code-branch.svg delete mode 100644 packages/grafana-icons/svg/cog.svg delete mode 100644 packages/grafana-icons/svg/columns.svg delete mode 100644 packages/grafana-icons/svg/comment-alt-message.svg delete mode 100644 packages/grafana-icons/svg/comment-alt-share.svg delete mode 100644 packages/grafana-icons/svg/comment-alt.svg delete mode 100644 packages/grafana-icons/svg/comments-alt.svg delete mode 100644 packages/grafana-icons/svg/compass.svg delete mode 100644 packages/grafana-icons/svg/copy.svg delete mode 100644 packages/grafana-icons/svg/corner-down-right-alt.svg delete mode 100644 packages/grafana-icons/svg/create-dashboard.svg delete mode 100644 packages/grafana-icons/svg/credit-card.svg delete mode 100644 packages/grafana-icons/svg/crosshair.svg delete mode 100644 packages/grafana-icons/svg/cube.svg delete mode 100644 packages/grafana-icons/svg/dashboard.svg delete mode 100644 packages/grafana-icons/svg/database.svg delete mode 100644 packages/grafana-icons/svg/dice-three.svg delete mode 100644 packages/grafana-icons/svg/discord.svg delete mode 100644 packages/grafana-icons/svg/docker.svg delete mode 100644 packages/grafana-icons/svg/document-info.svg delete mode 100644 packages/grafana-icons/svg/document-layout-left.svg delete mode 100644 packages/grafana-icons/svg/download-alt.svg delete mode 100644 packages/grafana-icons/svg/draggabledots.svg delete mode 100644 packages/grafana-icons/svg/drilldown.svg delete mode 100644 packages/grafana-icons/svg/edit.svg delete mode 100644 packages/grafana-icons/svg/ellipsis-h.svg delete mode 100644 packages/grafana-icons/svg/ellipsis-v.svg delete mode 100644 packages/grafana-icons/svg/enter.svg delete mode 100644 packages/grafana-icons/svg/envelope.svg delete mode 100644 packages/grafana-icons/svg/exchange-alt.svg delete mode 100644 packages/grafana-icons/svg/exclamation-circle.svg delete mode 100644 packages/grafana-icons/svg/exclamation-triangle.svg delete mode 100644 packages/grafana-icons/svg/expand-arrows.svg delete mode 100644 packages/grafana-icons/svg/external-link-alt.svg delete mode 100644 packages/grafana-icons/svg/eye-slash.svg delete mode 100644 packages/grafana-icons/svg/eye.svg delete mode 100644 packages/grafana-icons/svg/favorite.svg delete mode 100644 packages/grafana-icons/svg/file-alt.svg delete mode 100644 packages/grafana-icons/svg/file-blank.svg delete mode 100644 packages/grafana-icons/svg/file-copy-alt.svg delete mode 100644 packages/grafana-icons/svg/file-download.svg delete mode 100644 packages/grafana-icons/svg/file-edit-alt.svg delete mode 100644 packages/grafana-icons/svg/file-landscape-alt.svg delete mode 100644 packages/grafana-icons/svg/filter.svg delete mode 100644 packages/grafana-icons/svg/fire.svg delete mode 100644 packages/grafana-icons/svg/flip.svg delete mode 100644 packages/grafana-icons/svg/folder-open.svg delete mode 100644 packages/grafana-icons/svg/folder-plus.svg delete mode 100644 packages/grafana-icons/svg/folder-upload.svg delete mode 100644 packages/grafana-icons/svg/folder.svg delete mode 100644 packages/grafana-icons/svg/font.svg delete mode 100644 packages/grafana-icons/svg/forward.svg delete mode 100644 packages/grafana-icons/svg/frontend-observability.svg delete mode 100644 packages/grafana-icons/svg/github.svg delete mode 100644 packages/grafana-icons/svg/gitlab.svg delete mode 100644 packages/grafana-icons/svg/globe.svg delete mode 100644 packages/grafana-icons/svg/glue.svg delete mode 100644 packages/grafana-icons/svg/google-hangouts-alt.svg delete mode 100644 packages/grafana-icons/svg/google.svg delete mode 100644 packages/grafana-icons/svg/grafana.svg delete mode 100644 packages/grafana-icons/svg/graph-bar.svg delete mode 100644 packages/grafana-icons/svg/grid.svg delete mode 100644 packages/grafana-icons/svg/heart-break.svg delete mode 100644 packages/grafana-icons/svg/heart-rate.svg delete mode 100644 packages/grafana-icons/svg/heart.svg delete mode 100644 packages/grafana-icons/svg/hipchat.svg delete mode 100644 packages/grafana-icons/svg/history-alt.svg delete mode 100644 packages/grafana-icons/svg/history.svg delete mode 100644 packages/grafana-icons/svg/home-alt.svg delete mode 100644 packages/grafana-icons/svg/home.svg delete mode 100644 packages/grafana-icons/svg/horizontal-align-center.svg delete mode 100644 packages/grafana-icons/svg/horizontal-align-left.svg delete mode 100644 packages/grafana-icons/svg/horizontal-align-right.svg delete mode 100644 packages/grafana-icons/svg/hourglass.svg delete mode 100644 packages/grafana-icons/svg/import.svg delete mode 100644 packages/grafana-icons/svg/info-circle.svg delete mode 100644 packages/grafana-icons/svg/info.svg delete mode 100644 packages/grafana-icons/svg/interpolation-linear.svg delete mode 100644 packages/grafana-icons/svg/interpolation-smooth.svg delete mode 100644 packages/grafana-icons/svg/interpolation-step-after.svg delete mode 100644 packages/grafana-icons/svg/interpolation-step-before.svg delete mode 100644 packages/grafana-icons/svg/k6.svg delete mode 100644 packages/grafana-icons/svg/key-skeleton-alt.svg delete mode 100644 packages/grafana-icons/svg/keyboard.svg delete mode 100644 packages/grafana-icons/svg/landscape.svg delete mode 100644 packages/grafana-icons/svg/layer-group.svg delete mode 100644 packages/grafana-icons/svg/layers-alt.svg delete mode 100644 packages/grafana-icons/svg/layout-simple.svg delete mode 100644 packages/grafana-icons/svg/library-panel.svg delete mode 100644 packages/grafana-icons/svg/line-alt.svg delete mode 100644 packages/grafana-icons/svg/line.svg delete mode 100644 packages/grafana-icons/svg/link.svg delete mode 100644 packages/grafana-icons/svg/list-ol.svg delete mode 100644 packages/grafana-icons/svg/list-ui-alt.svg delete mode 100644 packages/grafana-icons/svg/list-ul.svg delete mode 100644 packages/grafana-icons/svg/lock.svg delete mode 100644 packages/grafana-icons/svg/logs.svg delete mode 100644 packages/grafana-icons/svg/map-marker-minus.svg delete mode 100644 packages/grafana-icons/svg/map-marker-plus.svg delete mode 100644 packages/grafana-icons/svg/map-marker.svg delete mode 100644 packages/grafana-icons/svg/message.svg delete mode 100644 packages/grafana-icons/svg/microsoft.svg delete mode 100644 packages/grafana-icons/svg/minus-circle.svg delete mode 100644 packages/grafana-icons/svg/minus.svg delete mode 100644 packages/grafana-icons/svg/ml.svg delete mode 100644 packages/grafana-icons/svg/mobile-android.svg delete mode 100644 packages/grafana-icons/svg/monitor.svg delete mode 100644 packages/grafana-icons/svg/movepane-left.svg delete mode 100644 packages/grafana-icons/svg/movepane-right.svg delete mode 100644 packages/grafana-icons/svg/okta.svg delete mode 100644 packages/grafana-icons/svg/pagerduty.svg delete mode 100644 packages/grafana-icons/svg/palette.svg delete mode 100644 packages/grafana-icons/svg/panel-add.svg delete mode 100644 packages/grafana-icons/svg/paragraph.svg delete mode 100644 packages/grafana-icons/svg/pathfinder-unite.svg delete mode 100644 packages/grafana-icons/svg/pause.svg delete mode 100644 packages/grafana-icons/svg/pen.svg delete mode 100644 packages/grafana-icons/svg/percentage.svg delete mode 100644 packages/grafana-icons/svg/pin.svg delete mode 100644 packages/grafana-icons/svg/play.svg delete mode 100644 packages/grafana-icons/svg/plug.svg delete mode 100644 packages/grafana-icons/svg/plus-circle.svg delete mode 100644 packages/grafana-icons/svg/plus-square.svg delete mode 100644 packages/grafana-icons/svg/plus.svg delete mode 100644 packages/grafana-icons/svg/portrait.svg delete mode 100644 packages/grafana-icons/svg/power.svg delete mode 100644 packages/grafana-icons/svg/presentation-play.svg delete mode 100644 packages/grafana-icons/svg/process.svg delete mode 100644 packages/grafana-icons/svg/prometheus.svg delete mode 100644 packages/grafana-icons/svg/question-circle.svg delete mode 100644 packages/grafana-icons/svg/record-audio.svg delete mode 100644 packages/grafana-icons/svg/repeat.svg delete mode 100644 packages/grafana-icons/svg/rocket.svg delete mode 100644 packages/grafana-icons/svg/rss.svg delete mode 100644 packages/grafana-icons/svg/ruler-combined.svg delete mode 100644 packages/grafana-icons/svg/save.svg delete mode 100644 packages/grafana-icons/svg/search-minus.svg delete mode 100644 packages/grafana-icons/svg/search-plus.svg delete mode 100644 packages/grafana-icons/svg/search.svg delete mode 100644 packages/grafana-icons/svg/service-account.svg delete mode 100644 packages/grafana-icons/svg/share-alt.svg delete mode 100644 packages/grafana-icons/svg/shield-exclamation.svg delete mode 100644 packages/grafana-icons/svg/shield.svg delete mode 100644 packages/grafana-icons/svg/show-context.svg delete mode 100644 packages/grafana-icons/svg/signal.svg delete mode 100644 packages/grafana-icons/svg/signin.svg delete mode 100644 packages/grafana-icons/svg/signout.svg delete mode 100644 packages/grafana-icons/svg/sitemap.svg delete mode 100644 packages/grafana-icons/svg/slack.svg delete mode 100644 packages/grafana-icons/svg/sliders-v-alt.svg delete mode 100644 packages/grafana-icons/svg/sort-amount-down.svg delete mode 100644 packages/grafana-icons/svg/sort-amount-up.svg delete mode 100644 packages/grafana-icons/svg/spinner.svg delete mode 100644 packages/grafana-icons/svg/square-shape.svg delete mode 100644 packages/grafana-icons/svg/star.svg delete mode 100644 packages/grafana-icons/svg/step-backward.svg delete mode 100644 packages/grafana-icons/svg/stopwatch-slash.svg delete mode 100644 packages/grafana-icons/svg/stopwatch.svg delete mode 100644 packages/grafana-icons/svg/sync-slash.svg delete mode 100644 packages/grafana-icons/svg/sync.svg delete mode 100644 packages/grafana-icons/svg/table-collapse-all.svg delete mode 100644 packages/grafana-icons/svg/table-expand-all.svg delete mode 100644 packages/grafana-icons/svg/table.svg delete mode 100644 packages/grafana-icons/svg/tag-alt.svg delete mode 100644 packages/grafana-icons/svg/telegram-alt.svg delete mode 100644 packages/grafana-icons/svg/text-fields.svg delete mode 100644 packages/grafana-icons/svg/thumbs-up.svg delete mode 100644 packages/grafana-icons/svg/times-circle.svg delete mode 100644 packages/grafana-icons/svg/times.svg delete mode 100644 packages/grafana-icons/svg/toggle-off.svg delete mode 100644 packages/grafana-icons/svg/toggle-on.svg delete mode 100644 packages/grafana-icons/svg/traces.svg delete mode 100644 packages/grafana-icons/svg/trash-alt.svg delete mode 100644 packages/grafana-icons/svg/unarchive.svg delete mode 100644 packages/grafana-icons/svg/unlock.svg delete mode 100644 packages/grafana-icons/svg/upload.svg delete mode 100644 packages/grafana-icons/svg/user-arrows.svg delete mode 100644 packages/grafana-icons/svg/user.svg delete mode 100644 packages/grafana-icons/svg/users-alt.svg delete mode 100644 packages/grafana-icons/svg/vertical-align-bottom.svg delete mode 100644 packages/grafana-icons/svg/vertical-align-center.svg delete mode 100644 packages/grafana-icons/svg/vertical-align-top.svg delete mode 100644 packages/grafana-icons/svg/web-section-alt.svg delete mode 100644 packages/grafana-icons/svg/wrap-text.svg delete mode 100644 packages/grafana-icons/svg/x.svg delete mode 100644 packages/grafana-icons/templates/icon.cjs delete mode 100644 packages/grafana-icons/templates/index.cjs delete mode 100644 packages/grafana-icons/tsconfig.build.json delete mode 100644 packages/grafana-icons/tsconfig.json diff --git a/package.json b/package.json index 5e9a44e8f99..1b0482f1c7c 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,6 @@ "plugin:build": "nx run-many -t build --projects='tag:scope:plugin'", "plugin:build:commit": "nx run-many -t build:commit --projects='tag:scope:plugin'", "plugin:build:dev": "nx run-many -t dev --projects='tag:scope:plugin' --maxParallel=100", - "generate-icons": "nx run grafana-icons:generate", "process-specs": "node --experimental-strip-types scripts/process-specs.ts", "generate-apis": "yarn process-specs && rtk-query-codegen-openapi ./scripts/generate-rtk-apis.ts" }, @@ -275,7 +274,6 @@ "@grafana/plugin-ui": "0.10.5", "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", - "@grafana/saga-icons": "workspace:*", "@grafana/scenes": "^6.8.1", "@grafana/scenes-react": "^6.8.1", "@grafana/schema": "workspace:*", @@ -436,7 +434,6 @@ "workspaces": { "packages": [ "packages/*", - "packages/!(grafana-icons)/**", "public/app/plugins/*/*", "e2e/test-plugins/*" ] diff --git a/packages/grafana-icons/.gitignore b/packages/grafana-icons/.gitignore deleted file mode 100644 index 61e79a90563..00000000000 --- a/packages/grafana-icons/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -.idea -dist -node_modules -compiled -/icons-gen/ -/src/icons-gen/ -/src/index.ts diff --git a/packages/grafana-icons/.svgrrc.cjs b/packages/grafana-icons/.svgrrc.cjs deleted file mode 100644 index 2c1c29451bf..00000000000 --- a/packages/grafana-icons/.svgrrc.cjs +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Reference: https://react-svgr.com/docs/options/ - */ -module.exports = { - icon: '{dir}/[name].gen.js', - typescript: true, - jsxRuntime: 'automatic', - outDir: './src/icons-gen', - template: require('./templates/icon.cjs'), - indexTemplate: require('./templates/index.cjs'), - memo: true, - svgoConfig: { - plugins: [ - // Sanitise the SVGs - 'removeScriptElement', - ], - }, - jsx: { - babelConfig: { - plugins: [ - // Remove fill and id attributes from SVG child elements - [ - '@svgr/babel-plugin-remove-jsx-attribute', - { - elements: ['path', 'g', 'clipPath'], - attributes: ['id', 'fill'], - }, - ], - ], - }, - }, -}; diff --git a/packages/grafana-icons/CHANGELOG.md b/packages/grafana-icons/CHANGELOG.md deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/grafana-icons/LICENSE_APACHE2 b/packages/grafana-icons/LICENSE_APACHE2 deleted file mode 100644 index 373dde574a0..00000000000 --- a/packages/grafana-icons/LICENSE_APACHE2 +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2015 Grafana Labs - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/grafana-icons/README.md b/packages/grafana-icons/README.md deleted file mode 100644 index 78cbf4bf9e3..00000000000 --- a/packages/grafana-icons/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Grafana Saga Icons - -This package contains the icon React components used in Grafana and Grafana plugins. - -## Uploading a new icon - -To add a new icon to the library, open a PR which adds the SVG file for the icon into the `svg` directory. The file should be named with the icon name in kebab-case. For example, if the icon name is `MyIcon`, the file should be named `my-icon.svg`. Once the PR is merged, the icon will be automatically generated and added to the library. - -## Development - -1. Clone the repository -2. Run `yarn install` -3. After the installation, the icon components can be found in the `src/icons-gen` directory. -4. To regenerate/update the components, run `yarn generate`. diff --git a/packages/grafana-icons/package.json b/packages/grafana-icons/package.json deleted file mode 100644 index d826d1f02ca..00000000000 --- a/packages/grafana-icons/package.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "name": "@grafana/saga-icons", - "version": "12.0.0-pre", - "private": true, - "description": "Icons for Grafana", - "author": "Grafana Labs", - "license": "Apache-2.0", - "sideEffects": false, - "repository": { - "type": "git", - "url": "https://github.com/grafana/grafana.git", - "directory": "packages/grafana-icons" - }, - "type": "module", - "main": "src/index.ts", - "types": "src/index.ts", - "publishConfig": { - "main": "./dist/index.js", - "module": "./dist/index.js", - "types": "./dist/index.d.mts", - "access": "public" - }, - "files": [ - "./dist", - "./README.md", - "./CHANGELOG.md", - "./LICENSE_APACHE2" - ], - "scripts": { - "clean": "rimraf ./dist ./compiled ./package.tgz ./src/icons-gen", - "generate": "yarn clean && npx @svgr/cli ./svg --silent && mv ./src/icons-gen/index.ts ./src", - "typecheck": "yarn generate && tsc --emitDeclarationOnly false --noEmit", - "lint": "eslint --ext .ts,.tsx ./src", - "prettier:check": "prettier --check --list-different=false --log-level=warn \"**/*.{ts,tsx,scss,md,mdx,json}\"", - "build": "yarn generate && rollup -c rollup.config.ts --configPlugin esbuild" - }, - "devDependencies": { - "@babel/core": "7.26.10", - "@grafana/tsconfig": "^2.0.0", - "@rollup/plugin-node-resolve": "^16.0.0", - "@rollup/plugin-typescript": "^12.1.0", - "@svgr/babel-plugin-remove-jsx-attribute": "^8.0.0", - "@svgr/cli": "^8.1.0", - "@svgr/core": "8.1.0", - "@svgr/plugin-jsx": "^8.1.0", - "@svgr/plugin-prettier": "^8.1.0", - "@svgr/plugin-svgo": "^8.1.0", - "@types/babel__core": "^7", - "@types/node": "22.12.0", - "@types/react": "18.3.18", - "@types/react-dom": "18.3.5", - "esbuild": "0.25.0", - "prettier": "3.4.2", - "react": "18.3.1", - "react-dom": "18.3.1", - "rimraf": "6.0.1", - "rollup": "^4.22.4", - "rollup-plugin-dts": "^6.1.1", - "rollup-plugin-esbuild": "6.2.0", - "rollup-plugin-node-externals": "8.0.0", - "ts-node": "10.9.2", - "typescript": "5.7.3" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - } -} diff --git a/packages/grafana-icons/project.json b/packages/grafana-icons/project.json deleted file mode 100644 index 7950b1939c0..00000000000 --- a/packages/grafana-icons/project.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "$schema": "../../node_modules/nx/schemas/project-schema.json", - "projectType": "library", - "tags": ["scope:package", "type:ui"], - "targets": { - "generate": { - "outputs": ["{projectRoot}/src/icons-gen"] - }, - "build": {} - } -} diff --git a/packages/grafana-icons/rollup.config.ts b/packages/grafana-icons/rollup.config.ts deleted file mode 100644 index fde6ca115b0..00000000000 --- a/packages/grafana-icons/rollup.config.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { createRequire } from 'node:module'; - -import { entryPoint, esmOutput, plugins, tsDeclarationOutput } from '../rollup.config.parts'; - -const rq = createRequire(import.meta.url); -const pkg = rq('./package.json'); - -export default [ - { - input: entryPoint, - plugins, - output: esmOutput(pkg, 'grafana-icons'), - }, - tsDeclarationOutput(pkg, { input: 'src/index.ts' }), -]; diff --git a/packages/grafana-icons/src/IconBase.tsx b/packages/grafana-icons/src/IconBase.tsx deleted file mode 100644 index 51b2d432d6c..00000000000 --- a/packages/grafana-icons/src/IconBase.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { SVGProps } from 'react'; - -export type IconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl' | 'xxxl'; - -export interface IconProps extends Omit, 'onLoad' | 'onError' | 'ref'> { - /** Size (width and height) of the icon. Defaults to "md" or 16x16px */ - size?: IconSize; - /** Render the title element with the provided text. - * More info: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title - */ - title?: string; - /** Color of the icon. Defaults to "currentColor" */ - color?: string; -} - -function getSvgSize(size: IconSize) { - const sizeMap = { - xs: 12, - sm: 14, - md: 16, - lg: 18, - xl: 24, - xxl: 36, - xxxl: 48, - }; - - return sizeMap[size] || 16; -} - -export const IconBase = ({ title, size = 'md', color = 'currentColor', ...props }: IconProps) => { - const svgSize = getSvgSize(size); - - return ( - - {title && {title}} - {props.children} - - ); -}; diff --git a/packages/grafana-icons/svg/adjust-circle.svg b/packages/grafana-icons/svg/adjust-circle.svg deleted file mode 100644 index 9671174c42b..00000000000 --- a/packages/grafana-icons/svg/adjust-circle.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/ai.svg b/packages/grafana-icons/svg/ai.svg deleted file mode 100644 index c02622a8ac1..00000000000 --- a/packages/grafana-icons/svg/ai.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/packages/grafana-icons/svg/align-left.svg b/packages/grafana-icons/svg/align-left.svg deleted file mode 100644 index 85db219a1b6..00000000000 --- a/packages/grafana-icons/svg/align-left.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/align-right.svg b/packages/grafana-icons/svg/align-right.svg deleted file mode 100644 index 21d27aca3c5..00000000000 --- a/packages/grafana-icons/svg/align-right.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/amazon.svg b/packages/grafana-icons/svg/amazon.svg deleted file mode 100644 index 0af93549ae4..00000000000 --- a/packages/grafana-icons/svg/amazon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/anchor.svg b/packages/grafana-icons/svg/anchor.svg deleted file mode 100644 index cc203977055..00000000000 --- a/packages/grafana-icons/svg/anchor.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/angle-double-down.svg b/packages/grafana-icons/svg/angle-double-down.svg deleted file mode 100644 index 848604c408e..00000000000 --- a/packages/grafana-icons/svg/angle-double-down.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/angle-double-right.svg b/packages/grafana-icons/svg/angle-double-right.svg deleted file mode 100644 index f24b2633bc3..00000000000 --- a/packages/grafana-icons/svg/angle-double-right.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/angle-double-up.svg b/packages/grafana-icons/svg/angle-double-up.svg deleted file mode 100644 index c496dc1fa23..00000000000 --- a/packages/grafana-icons/svg/angle-double-up.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/angle-down.svg b/packages/grafana-icons/svg/angle-down.svg deleted file mode 100644 index df3b9410b06..00000000000 --- a/packages/grafana-icons/svg/angle-down.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/angle-left.svg b/packages/grafana-icons/svg/angle-left.svg deleted file mode 100644 index 25cd536aa4a..00000000000 --- a/packages/grafana-icons/svg/angle-left.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/angle-right.svg b/packages/grafana-icons/svg/angle-right.svg deleted file mode 100644 index b21121fe607..00000000000 --- a/packages/grafana-icons/svg/angle-right.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/angle-up.svg b/packages/grafana-icons/svg/angle-up.svg deleted file mode 100644 index 1309a2ed311..00000000000 --- a/packages/grafana-icons/svg/angle-up.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/application-observability.svg b/packages/grafana-icons/svg/application-observability.svg deleted file mode 100644 index cc33bd99ddf..00000000000 --- a/packages/grafana-icons/svg/application-observability.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/packages/grafana-icons/svg/apps.svg b/packages/grafana-icons/svg/apps.svg deleted file mode 100644 index 7d51d736491..00000000000 --- a/packages/grafana-icons/svg/apps.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/archive-alt.svg b/packages/grafana-icons/svg/archive-alt.svg deleted file mode 100644 index 7c6243ba211..00000000000 --- a/packages/grafana-icons/svg/archive-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/arrow-down.svg b/packages/grafana-icons/svg/arrow-down.svg deleted file mode 100644 index 9bd09a5b156..00000000000 --- a/packages/grafana-icons/svg/arrow-down.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/arrow-from-right.svg b/packages/grafana-icons/svg/arrow-from-right.svg deleted file mode 100644 index 5a05790003e..00000000000 --- a/packages/grafana-icons/svg/arrow-from-right.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/arrow-left.svg b/packages/grafana-icons/svg/arrow-left.svg deleted file mode 100644 index 69253987040..00000000000 --- a/packages/grafana-icons/svg/arrow-left.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/arrow-random.svg b/packages/grafana-icons/svg/arrow-random.svg deleted file mode 100644 index 353d6c12d55..00000000000 --- a/packages/grafana-icons/svg/arrow-random.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/arrow-right.svg b/packages/grafana-icons/svg/arrow-right.svg deleted file mode 100644 index 2b2ada351c3..00000000000 --- a/packages/grafana-icons/svg/arrow-right.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/arrow-to-right.svg b/packages/grafana-icons/svg/arrow-to-right.svg deleted file mode 100644 index 494352362cf..00000000000 --- a/packages/grafana-icons/svg/arrow-to-right.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/arrow-up.svg b/packages/grafana-icons/svg/arrow-up.svg deleted file mode 100644 index b5771debac5..00000000000 --- a/packages/grafana-icons/svg/arrow-up.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/arrow.svg b/packages/grafana-icons/svg/arrow.svg deleted file mode 100644 index 2c1e398fe8c..00000000000 --- a/packages/grafana-icons/svg/arrow.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/arrows-h.svg b/packages/grafana-icons/svg/arrows-h.svg deleted file mode 100644 index e7a0619d71d..00000000000 --- a/packages/grafana-icons/svg/arrows-h.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/arrows-v.svg b/packages/grafana-icons/svg/arrows-v.svg deleted file mode 100644 index d1ff46cbc37..00000000000 --- a/packages/grafana-icons/svg/arrows-v.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/asserts.svg b/packages/grafana-icons/svg/asserts.svg deleted file mode 100644 index 225853a3ed2..00000000000 --- a/packages/grafana-icons/svg/asserts.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/grafana-icons/svg/at.svg b/packages/grafana-icons/svg/at.svg deleted file mode 100644 index b9956d10b03..00000000000 --- a/packages/grafana-icons/svg/at.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/backward.svg b/packages/grafana-icons/svg/backward.svg deleted file mode 100644 index 5697eac4c42..00000000000 --- a/packages/grafana-icons/svg/backward.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/bar-alignment-after.svg b/packages/grafana-icons/svg/bar-alignment-after.svg deleted file mode 100644 index 7db1f010421..00000000000 --- a/packages/grafana-icons/svg/bar-alignment-after.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/packages/grafana-icons/svg/bar-alignment-before.svg b/packages/grafana-icons/svg/bar-alignment-before.svg deleted file mode 100644 index f05102104d7..00000000000 --- a/packages/grafana-icons/svg/bar-alignment-before.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/packages/grafana-icons/svg/bar-alignment-center.svg b/packages/grafana-icons/svg/bar-alignment-center.svg deleted file mode 100644 index 7b25c441a46..00000000000 --- a/packages/grafana-icons/svg/bar-alignment-center.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/packages/grafana-icons/svg/bars.svg b/packages/grafana-icons/svg/bars.svg deleted file mode 100644 index b19cbe402cc..00000000000 --- a/packages/grafana-icons/svg/bars.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/bell-slash.svg b/packages/grafana-icons/svg/bell-slash.svg deleted file mode 100644 index 6914ed2af33..00000000000 --- a/packages/grafana-icons/svg/bell-slash.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/bell.svg b/packages/grafana-icons/svg/bell.svg deleted file mode 100644 index 3fbc40a490d..00000000000 --- a/packages/grafana-icons/svg/bell.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/bolt.svg b/packages/grafana-icons/svg/bolt.svg deleted file mode 100644 index 4d4e0e62669..00000000000 --- a/packages/grafana-icons/svg/bolt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/book-open.svg b/packages/grafana-icons/svg/book-open.svg deleted file mode 100644 index 91347309afc..00000000000 --- a/packages/grafana-icons/svg/book-open.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/book.svg b/packages/grafana-icons/svg/book.svg deleted file mode 100644 index 8e59539d7a4..00000000000 --- a/packages/grafana-icons/svg/book.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/bookmark.svg b/packages/grafana-icons/svg/bookmark.svg deleted file mode 100644 index 5acd3c8af13..00000000000 --- a/packages/grafana-icons/svg/bookmark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/brackets-curly.svg b/packages/grafana-icons/svg/brackets-curly.svg deleted file mode 100644 index 8d4ed131b40..00000000000 --- a/packages/grafana-icons/svg/brackets-curly.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/bug.svg b/packages/grafana-icons/svg/bug.svg deleted file mode 100644 index ae0f05488a6..00000000000 --- a/packages/grafana-icons/svg/bug.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/building.svg b/packages/grafana-icons/svg/building.svg deleted file mode 100644 index ce05d400a5f..00000000000 --- a/packages/grafana-icons/svg/building.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/calculator-alt.svg b/packages/grafana-icons/svg/calculator-alt.svg deleted file mode 100644 index ab108ad924a..00000000000 --- a/packages/grafana-icons/svg/calculator-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/calendar-alt.svg b/packages/grafana-icons/svg/calendar-alt.svg deleted file mode 100644 index 53524f428be..00000000000 --- a/packages/grafana-icons/svg/calendar-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/calendar-slash.svg b/packages/grafana-icons/svg/calendar-slash.svg deleted file mode 100644 index 7744addb1b5..00000000000 --- a/packages/grafana-icons/svg/calendar-slash.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/camera.svg b/packages/grafana-icons/svg/camera.svg deleted file mode 100644 index b5386e2ccb3..00000000000 --- a/packages/grafana-icons/svg/camera.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/cancel.svg b/packages/grafana-icons/svg/cancel.svg deleted file mode 100644 index 138499ab728..00000000000 --- a/packages/grafana-icons/svg/cancel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/capture.svg b/packages/grafana-icons/svg/capture.svg deleted file mode 100644 index a984e562a97..00000000000 --- a/packages/grafana-icons/svg/capture.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/channel-add.svg b/packages/grafana-icons/svg/channel-add.svg deleted file mode 100644 index feedb453498..00000000000 --- a/packages/grafana-icons/svg/channel-add.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/chart-line.svg b/packages/grafana-icons/svg/chart-line.svg deleted file mode 100644 index 8b697a3db48..00000000000 --- a/packages/grafana-icons/svg/chart-line.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/check-circle.svg b/packages/grafana-icons/svg/check-circle.svg deleted file mode 100644 index 0924f9b5b85..00000000000 --- a/packages/grafana-icons/svg/check-circle.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/check-square.svg b/packages/grafana-icons/svg/check-square.svg deleted file mode 100644 index 02d0c70d3cb..00000000000 --- a/packages/grafana-icons/svg/check-square.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/check.svg b/packages/grafana-icons/svg/check.svg deleted file mode 100644 index dec3efeb23a..00000000000 --- a/packages/grafana-icons/svg/check.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/circle-mono.svg b/packages/grafana-icons/svg/circle-mono.svg deleted file mode 100644 index 4f6385be522..00000000000 --- a/packages/grafana-icons/svg/circle-mono.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/grafana-icons/svg/circle.svg b/packages/grafana-icons/svg/circle.svg deleted file mode 100644 index bbb2e6352a8..00000000000 --- a/packages/grafana-icons/svg/circle.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/clipboard-alt.svg b/packages/grafana-icons/svg/clipboard-alt.svg deleted file mode 100644 index 11ccde65c29..00000000000 --- a/packages/grafana-icons/svg/clipboard-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/clock-nine.svg b/packages/grafana-icons/svg/clock-nine.svg deleted file mode 100644 index 8f71b53942e..00000000000 --- a/packages/grafana-icons/svg/clock-nine.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/cloud-download.svg b/packages/grafana-icons/svg/cloud-download.svg deleted file mode 100644 index 5ebba2cdf75..00000000000 --- a/packages/grafana-icons/svg/cloud-download.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/cloud-upload.svg b/packages/grafana-icons/svg/cloud-upload.svg deleted file mode 100644 index e90d346c2c5..00000000000 --- a/packages/grafana-icons/svg/cloud-upload.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/cloud.svg b/packages/grafana-icons/svg/cloud.svg deleted file mode 100644 index 925e54e7e28..00000000000 --- a/packages/grafana-icons/svg/cloud.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/code-branch.svg b/packages/grafana-icons/svg/code-branch.svg deleted file mode 100644 index cc74c697eaa..00000000000 --- a/packages/grafana-icons/svg/code-branch.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/cog.svg b/packages/grafana-icons/svg/cog.svg deleted file mode 100644 index 982d8663e4a..00000000000 --- a/packages/grafana-icons/svg/cog.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/columns.svg b/packages/grafana-icons/svg/columns.svg deleted file mode 100644 index d61c50e6135..00000000000 --- a/packages/grafana-icons/svg/columns.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/comment-alt-message.svg b/packages/grafana-icons/svg/comment-alt-message.svg deleted file mode 100644 index 09dacb6dc9c..00000000000 --- a/packages/grafana-icons/svg/comment-alt-message.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/comment-alt-share.svg b/packages/grafana-icons/svg/comment-alt-share.svg deleted file mode 100644 index c9cd3c82c2d..00000000000 --- a/packages/grafana-icons/svg/comment-alt-share.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/comment-alt.svg b/packages/grafana-icons/svg/comment-alt.svg deleted file mode 100644 index 4d8da86f85f..00000000000 --- a/packages/grafana-icons/svg/comment-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/comments-alt.svg b/packages/grafana-icons/svg/comments-alt.svg deleted file mode 100644 index 98a060f283a..00000000000 --- a/packages/grafana-icons/svg/comments-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/compass.svg b/packages/grafana-icons/svg/compass.svg deleted file mode 100644 index 2055f01a768..00000000000 --- a/packages/grafana-icons/svg/compass.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/copy.svg b/packages/grafana-icons/svg/copy.svg deleted file mode 100644 index 1404ed257ae..00000000000 --- a/packages/grafana-icons/svg/copy.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/corner-down-right-alt.svg b/packages/grafana-icons/svg/corner-down-right-alt.svg deleted file mode 100644 index 2dbd839e3a4..00000000000 --- a/packages/grafana-icons/svg/corner-down-right-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/create-dashboard.svg b/packages/grafana-icons/svg/create-dashboard.svg deleted file mode 100644 index de8a94e7461..00000000000 --- a/packages/grafana-icons/svg/create-dashboard.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/credit-card.svg b/packages/grafana-icons/svg/credit-card.svg deleted file mode 100644 index 4f74502ab49..00000000000 --- a/packages/grafana-icons/svg/credit-card.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/crosshair.svg b/packages/grafana-icons/svg/crosshair.svg deleted file mode 100644 index 1bdcded94e4..00000000000 --- a/packages/grafana-icons/svg/crosshair.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/cube.svg b/packages/grafana-icons/svg/cube.svg deleted file mode 100644 index c365b525970..00000000000 --- a/packages/grafana-icons/svg/cube.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/dashboard.svg b/packages/grafana-icons/svg/dashboard.svg deleted file mode 100644 index 5e698696457..00000000000 --- a/packages/grafana-icons/svg/dashboard.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/database.svg b/packages/grafana-icons/svg/database.svg deleted file mode 100644 index dc7d33efc85..00000000000 --- a/packages/grafana-icons/svg/database.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/dice-three.svg b/packages/grafana-icons/svg/dice-three.svg deleted file mode 100644 index 041d80aa100..00000000000 --- a/packages/grafana-icons/svg/dice-three.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/discord.svg b/packages/grafana-icons/svg/discord.svg deleted file mode 100644 index 149ee80d876..00000000000 --- a/packages/grafana-icons/svg/discord.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/docker.svg b/packages/grafana-icons/svg/docker.svg deleted file mode 100644 index 553dbd587ba..00000000000 --- a/packages/grafana-icons/svg/docker.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/document-info.svg b/packages/grafana-icons/svg/document-info.svg deleted file mode 100644 index dc5b17e4e6d..00000000000 --- a/packages/grafana-icons/svg/document-info.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/document-layout-left.svg b/packages/grafana-icons/svg/document-layout-left.svg deleted file mode 100644 index f7b798f4d8c..00000000000 --- a/packages/grafana-icons/svg/document-layout-left.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/download-alt.svg b/packages/grafana-icons/svg/download-alt.svg deleted file mode 100644 index 574f0a1b9d2..00000000000 --- a/packages/grafana-icons/svg/download-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/draggabledots.svg b/packages/grafana-icons/svg/draggabledots.svg deleted file mode 100644 index 4e5d98a7214..00000000000 --- a/packages/grafana-icons/svg/draggabledots.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/drilldown.svg b/packages/grafana-icons/svg/drilldown.svg deleted file mode 100644 index 27f627ce19f..00000000000 --- a/packages/grafana-icons/svg/drilldown.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/grafana-icons/svg/edit.svg b/packages/grafana-icons/svg/edit.svg deleted file mode 100644 index 62b8946863f..00000000000 --- a/packages/grafana-icons/svg/edit.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/ellipsis-h.svg b/packages/grafana-icons/svg/ellipsis-h.svg deleted file mode 100644 index 629a1208793..00000000000 --- a/packages/grafana-icons/svg/ellipsis-h.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/ellipsis-v.svg b/packages/grafana-icons/svg/ellipsis-v.svg deleted file mode 100644 index 676776e64b0..00000000000 --- a/packages/grafana-icons/svg/ellipsis-v.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/enter.svg b/packages/grafana-icons/svg/enter.svg deleted file mode 100644 index 7458836ccb1..00000000000 --- a/packages/grafana-icons/svg/enter.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/envelope.svg b/packages/grafana-icons/svg/envelope.svg deleted file mode 100644 index f88dd1dca63..00000000000 --- a/packages/grafana-icons/svg/envelope.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/exchange-alt.svg b/packages/grafana-icons/svg/exchange-alt.svg deleted file mode 100644 index b36fd9ed0a0..00000000000 --- a/packages/grafana-icons/svg/exchange-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/exclamation-circle.svg b/packages/grafana-icons/svg/exclamation-circle.svg deleted file mode 100644 index ed889cdbb50..00000000000 --- a/packages/grafana-icons/svg/exclamation-circle.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/exclamation-triangle.svg b/packages/grafana-icons/svg/exclamation-triangle.svg deleted file mode 100644 index 49d4ec27805..00000000000 --- a/packages/grafana-icons/svg/exclamation-triangle.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/expand-arrows.svg b/packages/grafana-icons/svg/expand-arrows.svg deleted file mode 100644 index 906d9fff618..00000000000 --- a/packages/grafana-icons/svg/expand-arrows.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/external-link-alt.svg b/packages/grafana-icons/svg/external-link-alt.svg deleted file mode 100644 index 196abdfa19a..00000000000 --- a/packages/grafana-icons/svg/external-link-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/eye-slash.svg b/packages/grafana-icons/svg/eye-slash.svg deleted file mode 100644 index 702f0a87f43..00000000000 --- a/packages/grafana-icons/svg/eye-slash.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/eye.svg b/packages/grafana-icons/svg/eye.svg deleted file mode 100644 index 5f7293fa112..00000000000 --- a/packages/grafana-icons/svg/eye.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/favorite.svg b/packages/grafana-icons/svg/favorite.svg deleted file mode 100644 index 3a22c296ccd..00000000000 --- a/packages/grafana-icons/svg/favorite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/file-alt.svg b/packages/grafana-icons/svg/file-alt.svg deleted file mode 100644 index d2f338143ec..00000000000 --- a/packages/grafana-icons/svg/file-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/file-blank.svg b/packages/grafana-icons/svg/file-blank.svg deleted file mode 100644 index 3462dc71d8a..00000000000 --- a/packages/grafana-icons/svg/file-blank.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/file-copy-alt.svg b/packages/grafana-icons/svg/file-copy-alt.svg deleted file mode 100644 index 578263caf26..00000000000 --- a/packages/grafana-icons/svg/file-copy-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/file-download.svg b/packages/grafana-icons/svg/file-download.svg deleted file mode 100644 index 2a3e7495c15..00000000000 --- a/packages/grafana-icons/svg/file-download.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/file-edit-alt.svg b/packages/grafana-icons/svg/file-edit-alt.svg deleted file mode 100644 index 71edd3f2fac..00000000000 --- a/packages/grafana-icons/svg/file-edit-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/file-landscape-alt.svg b/packages/grafana-icons/svg/file-landscape-alt.svg deleted file mode 100644 index 61d665cf6af..00000000000 --- a/packages/grafana-icons/svg/file-landscape-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/filter.svg b/packages/grafana-icons/svg/filter.svg deleted file mode 100644 index f7f8681c111..00000000000 --- a/packages/grafana-icons/svg/filter.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/fire.svg b/packages/grafana-icons/svg/fire.svg deleted file mode 100644 index 1d6d8f316b1..00000000000 --- a/packages/grafana-icons/svg/fire.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/flip.svg b/packages/grafana-icons/svg/flip.svg deleted file mode 100644 index df17cbe6418..00000000000 --- a/packages/grafana-icons/svg/flip.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/grafana-icons/svg/folder-open.svg b/packages/grafana-icons/svg/folder-open.svg deleted file mode 100644 index 5ce79195e8e..00000000000 --- a/packages/grafana-icons/svg/folder-open.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/folder-plus.svg b/packages/grafana-icons/svg/folder-plus.svg deleted file mode 100644 index 8c0fba6aaed..00000000000 --- a/packages/grafana-icons/svg/folder-plus.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/folder-upload.svg b/packages/grafana-icons/svg/folder-upload.svg deleted file mode 100644 index f911607d90e..00000000000 --- a/packages/grafana-icons/svg/folder-upload.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/folder.svg b/packages/grafana-icons/svg/folder.svg deleted file mode 100644 index 0127e9bda6a..00000000000 --- a/packages/grafana-icons/svg/folder.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/font.svg b/packages/grafana-icons/svg/font.svg deleted file mode 100644 index fca165f8fb1..00000000000 --- a/packages/grafana-icons/svg/font.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/forward.svg b/packages/grafana-icons/svg/forward.svg deleted file mode 100644 index d0f27c5d589..00000000000 --- a/packages/grafana-icons/svg/forward.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/frontend-observability.svg b/packages/grafana-icons/svg/frontend-observability.svg deleted file mode 100644 index db73e4f5181..00000000000 --- a/packages/grafana-icons/svg/frontend-observability.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/grafana-icons/svg/github.svg b/packages/grafana-icons/svg/github.svg deleted file mode 100644 index 24024a647b9..00000000000 --- a/packages/grafana-icons/svg/github.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/gitlab.svg b/packages/grafana-icons/svg/gitlab.svg deleted file mode 100644 index 51ae5c8a521..00000000000 --- a/packages/grafana-icons/svg/gitlab.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/globe.svg b/packages/grafana-icons/svg/globe.svg deleted file mode 100644 index 714fd374c8e..00000000000 --- a/packages/grafana-icons/svg/globe.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/glue.svg b/packages/grafana-icons/svg/glue.svg deleted file mode 100644 index 647a7822322..00000000000 --- a/packages/grafana-icons/svg/glue.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/packages/grafana-icons/svg/google-hangouts-alt.svg b/packages/grafana-icons/svg/google-hangouts-alt.svg deleted file mode 100644 index 6daa88dcd9d..00000000000 --- a/packages/grafana-icons/svg/google-hangouts-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/google.svg b/packages/grafana-icons/svg/google.svg deleted file mode 100644 index 80340154985..00000000000 --- a/packages/grafana-icons/svg/google.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/grafana.svg b/packages/grafana-icons/svg/grafana.svg deleted file mode 100644 index 9b00ba0cb0b..00000000000 --- a/packages/grafana-icons/svg/grafana.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/graph-bar.svg b/packages/grafana-icons/svg/graph-bar.svg deleted file mode 100644 index af01d934918..00000000000 --- a/packages/grafana-icons/svg/graph-bar.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/grid.svg b/packages/grafana-icons/svg/grid.svg deleted file mode 100644 index cd064809dcb..00000000000 --- a/packages/grafana-icons/svg/grid.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/grafana-icons/svg/heart-break.svg b/packages/grafana-icons/svg/heart-break.svg deleted file mode 100644 index 75bec55422d..00000000000 --- a/packages/grafana-icons/svg/heart-break.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/heart-rate.svg b/packages/grafana-icons/svg/heart-rate.svg deleted file mode 100644 index ff779bb345c..00000000000 --- a/packages/grafana-icons/svg/heart-rate.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/heart.svg b/packages/grafana-icons/svg/heart.svg deleted file mode 100644 index 2d0107bcbfb..00000000000 --- a/packages/grafana-icons/svg/heart.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/hipchat.svg b/packages/grafana-icons/svg/hipchat.svg deleted file mode 100644 index c949ad9a4a1..00000000000 --- a/packages/grafana-icons/svg/hipchat.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/history-alt.svg b/packages/grafana-icons/svg/history-alt.svg deleted file mode 100644 index 10d42733580..00000000000 --- a/packages/grafana-icons/svg/history-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/history.svg b/packages/grafana-icons/svg/history.svg deleted file mode 100644 index c85b770303f..00000000000 --- a/packages/grafana-icons/svg/history.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/home-alt.svg b/packages/grafana-icons/svg/home-alt.svg deleted file mode 100644 index 798130de6d8..00000000000 --- a/packages/grafana-icons/svg/home-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/home.svg b/packages/grafana-icons/svg/home.svg deleted file mode 100644 index a937bb665ac..00000000000 --- a/packages/grafana-icons/svg/home.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/horizontal-align-center.svg b/packages/grafana-icons/svg/horizontal-align-center.svg deleted file mode 100644 index f702b34e173..00000000000 --- a/packages/grafana-icons/svg/horizontal-align-center.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/horizontal-align-left.svg b/packages/grafana-icons/svg/horizontal-align-left.svg deleted file mode 100644 index 5f115c70403..00000000000 --- a/packages/grafana-icons/svg/horizontal-align-left.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/horizontal-align-right.svg b/packages/grafana-icons/svg/horizontal-align-right.svg deleted file mode 100644 index 7a9266d650c..00000000000 --- a/packages/grafana-icons/svg/horizontal-align-right.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/hourglass.svg b/packages/grafana-icons/svg/hourglass.svg deleted file mode 100644 index 3a1946bfab6..00000000000 --- a/packages/grafana-icons/svg/hourglass.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/import.svg b/packages/grafana-icons/svg/import.svg deleted file mode 100644 index 0eff75e6d76..00000000000 --- a/packages/grafana-icons/svg/import.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/info-circle.svg b/packages/grafana-icons/svg/info-circle.svg deleted file mode 100644 index 8047ca198c2..00000000000 --- a/packages/grafana-icons/svg/info-circle.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/info.svg b/packages/grafana-icons/svg/info.svg deleted file mode 100644 index ff73496d9be..00000000000 --- a/packages/grafana-icons/svg/info.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/interpolation-linear.svg b/packages/grafana-icons/svg/interpolation-linear.svg deleted file mode 100644 index 3a3ce70c604..00000000000 --- a/packages/grafana-icons/svg/interpolation-linear.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/packages/grafana-icons/svg/interpolation-smooth.svg b/packages/grafana-icons/svg/interpolation-smooth.svg deleted file mode 100644 index 6674465f9e3..00000000000 --- a/packages/grafana-icons/svg/interpolation-smooth.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/packages/grafana-icons/svg/interpolation-step-after.svg b/packages/grafana-icons/svg/interpolation-step-after.svg deleted file mode 100644 index 6b653aa7a1d..00000000000 --- a/packages/grafana-icons/svg/interpolation-step-after.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/packages/grafana-icons/svg/interpolation-step-before.svg b/packages/grafana-icons/svg/interpolation-step-before.svg deleted file mode 100644 index 0566b386158..00000000000 --- a/packages/grafana-icons/svg/interpolation-step-before.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/packages/grafana-icons/svg/k6.svg b/packages/grafana-icons/svg/k6.svg deleted file mode 100644 index ba5cd2d7e39..00000000000 --- a/packages/grafana-icons/svg/k6.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/packages/grafana-icons/svg/key-skeleton-alt.svg b/packages/grafana-icons/svg/key-skeleton-alt.svg deleted file mode 100644 index e3c7f73e37a..00000000000 --- a/packages/grafana-icons/svg/key-skeleton-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/keyboard.svg b/packages/grafana-icons/svg/keyboard.svg deleted file mode 100644 index 06e3e2cf078..00000000000 --- a/packages/grafana-icons/svg/keyboard.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/landscape.svg b/packages/grafana-icons/svg/landscape.svg deleted file mode 100644 index a91a3611bb9..00000000000 --- a/packages/grafana-icons/svg/landscape.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/grafana-icons/svg/layer-group.svg b/packages/grafana-icons/svg/layer-group.svg deleted file mode 100644 index 5fa2364dd8c..00000000000 --- a/packages/grafana-icons/svg/layer-group.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/layers-alt.svg b/packages/grafana-icons/svg/layers-alt.svg deleted file mode 100644 index 7e02288ffb1..00000000000 --- a/packages/grafana-icons/svg/layers-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/layout-simple.svg b/packages/grafana-icons/svg/layout-simple.svg deleted file mode 100644 index aad4b31beb5..00000000000 --- a/packages/grafana-icons/svg/layout-simple.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/grafana-icons/svg/library-panel.svg b/packages/grafana-icons/svg/library-panel.svg deleted file mode 100644 index 4cd65511bc3..00000000000 --- a/packages/grafana-icons/svg/library-panel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/line-alt.svg b/packages/grafana-icons/svg/line-alt.svg deleted file mode 100644 index 55d857e2251..00000000000 --- a/packages/grafana-icons/svg/line-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/line.svg b/packages/grafana-icons/svg/line.svg deleted file mode 100644 index 012c4f38b99..00000000000 --- a/packages/grafana-icons/svg/line.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/link.svg b/packages/grafana-icons/svg/link.svg deleted file mode 100644 index e43871700df..00000000000 --- a/packages/grafana-icons/svg/link.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/list-ol.svg b/packages/grafana-icons/svg/list-ol.svg deleted file mode 100644 index c593877c8b5..00000000000 --- a/packages/grafana-icons/svg/list-ol.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/list-ui-alt.svg b/packages/grafana-icons/svg/list-ui-alt.svg deleted file mode 100644 index 6b9d95d3374..00000000000 --- a/packages/grafana-icons/svg/list-ui-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/list-ul.svg b/packages/grafana-icons/svg/list-ul.svg deleted file mode 100644 index 75fcddf48e7..00000000000 --- a/packages/grafana-icons/svg/list-ul.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/lock.svg b/packages/grafana-icons/svg/lock.svg deleted file mode 100644 index 3ca5d82e36e..00000000000 --- a/packages/grafana-icons/svg/lock.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/logs.svg b/packages/grafana-icons/svg/logs.svg deleted file mode 100644 index 6e7a400975f..00000000000 --- a/packages/grafana-icons/svg/logs.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/packages/grafana-icons/svg/map-marker-minus.svg b/packages/grafana-icons/svg/map-marker-minus.svg deleted file mode 100644 index 55c2aa5b9f7..00000000000 --- a/packages/grafana-icons/svg/map-marker-minus.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/map-marker-plus.svg b/packages/grafana-icons/svg/map-marker-plus.svg deleted file mode 100644 index b0b7202d71d..00000000000 --- a/packages/grafana-icons/svg/map-marker-plus.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/map-marker.svg b/packages/grafana-icons/svg/map-marker.svg deleted file mode 100644 index 4d8d8109015..00000000000 --- a/packages/grafana-icons/svg/map-marker.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/message.svg b/packages/grafana-icons/svg/message.svg deleted file mode 100644 index e127f808248..00000000000 --- a/packages/grafana-icons/svg/message.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/microsoft.svg b/packages/grafana-icons/svg/microsoft.svg deleted file mode 100644 index 9c68120a1b1..00000000000 --- a/packages/grafana-icons/svg/microsoft.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/minus-circle.svg b/packages/grafana-icons/svg/minus-circle.svg deleted file mode 100644 index 95f0d31db2b..00000000000 --- a/packages/grafana-icons/svg/minus-circle.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/minus.svg b/packages/grafana-icons/svg/minus.svg deleted file mode 100644 index 1431a2752b4..00000000000 --- a/packages/grafana-icons/svg/minus.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/ml.svg b/packages/grafana-icons/svg/ml.svg deleted file mode 100644 index 3ebe54581e7..00000000000 --- a/packages/grafana-icons/svg/ml.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/mobile-android.svg b/packages/grafana-icons/svg/mobile-android.svg deleted file mode 100644 index 19662540cf6..00000000000 --- a/packages/grafana-icons/svg/mobile-android.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/monitor.svg b/packages/grafana-icons/svg/monitor.svg deleted file mode 100644 index 2510a7f1997..00000000000 --- a/packages/grafana-icons/svg/monitor.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/movepane-left.svg b/packages/grafana-icons/svg/movepane-left.svg deleted file mode 100644 index bcb0292c74f..00000000000 --- a/packages/grafana-icons/svg/movepane-left.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/movepane-right.svg b/packages/grafana-icons/svg/movepane-right.svg deleted file mode 100644 index 61c8e3adad1..00000000000 --- a/packages/grafana-icons/svg/movepane-right.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/okta.svg b/packages/grafana-icons/svg/okta.svg deleted file mode 100644 index f48ff1bc573..00000000000 --- a/packages/grafana-icons/svg/okta.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/pagerduty.svg b/packages/grafana-icons/svg/pagerduty.svg deleted file mode 100644 index 9a7ac015f0f..00000000000 --- a/packages/grafana-icons/svg/pagerduty.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/palette.svg b/packages/grafana-icons/svg/palette.svg deleted file mode 100644 index 12d5e2f8132..00000000000 --- a/packages/grafana-icons/svg/palette.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/panel-add.svg b/packages/grafana-icons/svg/panel-add.svg deleted file mode 100644 index bb1706b3e02..00000000000 --- a/packages/grafana-icons/svg/panel-add.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/paragraph.svg b/packages/grafana-icons/svg/paragraph.svg deleted file mode 100644 index 422c44fe9cb..00000000000 --- a/packages/grafana-icons/svg/paragraph.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/pathfinder-unite.svg b/packages/grafana-icons/svg/pathfinder-unite.svg deleted file mode 100644 index da16fde20ba..00000000000 --- a/packages/grafana-icons/svg/pathfinder-unite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/pause.svg b/packages/grafana-icons/svg/pause.svg deleted file mode 100644 index 06fb2270365..00000000000 --- a/packages/grafana-icons/svg/pause.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/pen.svg b/packages/grafana-icons/svg/pen.svg deleted file mode 100644 index e10bfd8c9b3..00000000000 --- a/packages/grafana-icons/svg/pen.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/percentage.svg b/packages/grafana-icons/svg/percentage.svg deleted file mode 100644 index ace24e2d770..00000000000 --- a/packages/grafana-icons/svg/percentage.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/pin.svg b/packages/grafana-icons/svg/pin.svg deleted file mode 100644 index 70c45e48f02..00000000000 --- a/packages/grafana-icons/svg/pin.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/packages/grafana-icons/svg/play.svg b/packages/grafana-icons/svg/play.svg deleted file mode 100644 index 98d8d491daf..00000000000 --- a/packages/grafana-icons/svg/play.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/plug.svg b/packages/grafana-icons/svg/plug.svg deleted file mode 100644 index 9abe3b8e69a..00000000000 --- a/packages/grafana-icons/svg/plug.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/plus-circle.svg b/packages/grafana-icons/svg/plus-circle.svg deleted file mode 100644 index 77a9425985c..00000000000 --- a/packages/grafana-icons/svg/plus-circle.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/plus-square.svg b/packages/grafana-icons/svg/plus-square.svg deleted file mode 100644 index 3876d5201bb..00000000000 --- a/packages/grafana-icons/svg/plus-square.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/plus.svg b/packages/grafana-icons/svg/plus.svg deleted file mode 100644 index 399f74920d5..00000000000 --- a/packages/grafana-icons/svg/plus.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/portrait.svg b/packages/grafana-icons/svg/portrait.svg deleted file mode 100644 index 5494958b439..00000000000 --- a/packages/grafana-icons/svg/portrait.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/grafana-icons/svg/power.svg b/packages/grafana-icons/svg/power.svg deleted file mode 100644 index 71986c63085..00000000000 --- a/packages/grafana-icons/svg/power.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/presentation-play.svg b/packages/grafana-icons/svg/presentation-play.svg deleted file mode 100644 index bcb27f2502e..00000000000 --- a/packages/grafana-icons/svg/presentation-play.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/process.svg b/packages/grafana-icons/svg/process.svg deleted file mode 100644 index b2f6516ab42..00000000000 --- a/packages/grafana-icons/svg/process.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/prometheus.svg b/packages/grafana-icons/svg/prometheus.svg deleted file mode 100644 index 8a33b98303f..00000000000 --- a/packages/grafana-icons/svg/prometheus.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/grafana-icons/svg/question-circle.svg b/packages/grafana-icons/svg/question-circle.svg deleted file mode 100644 index fb077c98532..00000000000 --- a/packages/grafana-icons/svg/question-circle.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/record-audio.svg b/packages/grafana-icons/svg/record-audio.svg deleted file mode 100644 index 6f6a8154d18..00000000000 --- a/packages/grafana-icons/svg/record-audio.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/repeat.svg b/packages/grafana-icons/svg/repeat.svg deleted file mode 100644 index 7edc1116fa9..00000000000 --- a/packages/grafana-icons/svg/repeat.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/rocket.svg b/packages/grafana-icons/svg/rocket.svg deleted file mode 100644 index ae71c119ce5..00000000000 --- a/packages/grafana-icons/svg/rocket.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/rss.svg b/packages/grafana-icons/svg/rss.svg deleted file mode 100644 index 5d0c50c11d6..00000000000 --- a/packages/grafana-icons/svg/rss.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/ruler-combined.svg b/packages/grafana-icons/svg/ruler-combined.svg deleted file mode 100644 index aab5576c1bd..00000000000 --- a/packages/grafana-icons/svg/ruler-combined.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/save.svg b/packages/grafana-icons/svg/save.svg deleted file mode 100644 index f40d3ac0bca..00000000000 --- a/packages/grafana-icons/svg/save.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/search-minus.svg b/packages/grafana-icons/svg/search-minus.svg deleted file mode 100644 index 2ce6e0aa784..00000000000 --- a/packages/grafana-icons/svg/search-minus.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/search-plus.svg b/packages/grafana-icons/svg/search-plus.svg deleted file mode 100644 index 18f99d89c22..00000000000 --- a/packages/grafana-icons/svg/search-plus.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/search.svg b/packages/grafana-icons/svg/search.svg deleted file mode 100644 index 39f5b14c3a9..00000000000 --- a/packages/grafana-icons/svg/search.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/service-account.svg b/packages/grafana-icons/svg/service-account.svg deleted file mode 100644 index 6027c4b676f..00000000000 --- a/packages/grafana-icons/svg/service-account.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/packages/grafana-icons/svg/share-alt.svg b/packages/grafana-icons/svg/share-alt.svg deleted file mode 100644 index d153312fd96..00000000000 --- a/packages/grafana-icons/svg/share-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/shield-exclamation.svg b/packages/grafana-icons/svg/shield-exclamation.svg deleted file mode 100644 index 273b02b114b..00000000000 --- a/packages/grafana-icons/svg/shield-exclamation.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/shield.svg b/packages/grafana-icons/svg/shield.svg deleted file mode 100644 index 736f02a06d5..00000000000 --- a/packages/grafana-icons/svg/shield.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/show-context.svg b/packages/grafana-icons/svg/show-context.svg deleted file mode 100644 index fb26fd6f3eb..00000000000 --- a/packages/grafana-icons/svg/show-context.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/packages/grafana-icons/svg/signal.svg b/packages/grafana-icons/svg/signal.svg deleted file mode 100644 index 95523452c22..00000000000 --- a/packages/grafana-icons/svg/signal.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/signin.svg b/packages/grafana-icons/svg/signin.svg deleted file mode 100644 index a3ac7e9b9b3..00000000000 --- a/packages/grafana-icons/svg/signin.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/signout.svg b/packages/grafana-icons/svg/signout.svg deleted file mode 100644 index 4009a2654bb..00000000000 --- a/packages/grafana-icons/svg/signout.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/sitemap.svg b/packages/grafana-icons/svg/sitemap.svg deleted file mode 100644 index 3c5cd8d91dd..00000000000 --- a/packages/grafana-icons/svg/sitemap.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/slack.svg b/packages/grafana-icons/svg/slack.svg deleted file mode 100644 index edf70d18bdb..00000000000 --- a/packages/grafana-icons/svg/slack.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/sliders-v-alt.svg b/packages/grafana-icons/svg/sliders-v-alt.svg deleted file mode 100644 index 868914ed2bc..00000000000 --- a/packages/grafana-icons/svg/sliders-v-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/sort-amount-down.svg b/packages/grafana-icons/svg/sort-amount-down.svg deleted file mode 100644 index f120ee0115b..00000000000 --- a/packages/grafana-icons/svg/sort-amount-down.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/sort-amount-up.svg b/packages/grafana-icons/svg/sort-amount-up.svg deleted file mode 100644 index 076ebf98af2..00000000000 --- a/packages/grafana-icons/svg/sort-amount-up.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/spinner.svg b/packages/grafana-icons/svg/spinner.svg deleted file mode 100644 index 62eb684eb27..00000000000 --- a/packages/grafana-icons/svg/spinner.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/grafana-icons/svg/square-shape.svg b/packages/grafana-icons/svg/square-shape.svg deleted file mode 100644 index 960560f6553..00000000000 --- a/packages/grafana-icons/svg/square-shape.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/star.svg b/packages/grafana-icons/svg/star.svg deleted file mode 100644 index 7fc7a836a7e..00000000000 --- a/packages/grafana-icons/svg/star.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/step-backward.svg b/packages/grafana-icons/svg/step-backward.svg deleted file mode 100644 index b8832353209..00000000000 --- a/packages/grafana-icons/svg/step-backward.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/stopwatch-slash.svg b/packages/grafana-icons/svg/stopwatch-slash.svg deleted file mode 100644 index e220fda992a..00000000000 --- a/packages/grafana-icons/svg/stopwatch-slash.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/stopwatch.svg b/packages/grafana-icons/svg/stopwatch.svg deleted file mode 100644 index 042caabbcfe..00000000000 --- a/packages/grafana-icons/svg/stopwatch.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/sync-slash.svg b/packages/grafana-icons/svg/sync-slash.svg deleted file mode 100644 index a4e908a513a..00000000000 --- a/packages/grafana-icons/svg/sync-slash.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/sync.svg b/packages/grafana-icons/svg/sync.svg deleted file mode 100644 index efb196ca58a..00000000000 --- a/packages/grafana-icons/svg/sync.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/table-collapse-all.svg b/packages/grafana-icons/svg/table-collapse-all.svg deleted file mode 100644 index bacf4ac77a9..00000000000 --- a/packages/grafana-icons/svg/table-collapse-all.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/packages/grafana-icons/svg/table-expand-all.svg b/packages/grafana-icons/svg/table-expand-all.svg deleted file mode 100644 index 06682bc0605..00000000000 --- a/packages/grafana-icons/svg/table-expand-all.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/packages/grafana-icons/svg/table.svg b/packages/grafana-icons/svg/table.svg deleted file mode 100644 index 9d733a3450a..00000000000 --- a/packages/grafana-icons/svg/table.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/tag-alt.svg b/packages/grafana-icons/svg/tag-alt.svg deleted file mode 100644 index 992db25baf9..00000000000 --- a/packages/grafana-icons/svg/tag-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/telegram-alt.svg b/packages/grafana-icons/svg/telegram-alt.svg deleted file mode 100644 index 46aaad98400..00000000000 --- a/packages/grafana-icons/svg/telegram-alt.svg +++ /dev/null @@ -1 +0,0 @@ -telegram \ No newline at end of file diff --git a/packages/grafana-icons/svg/text-fields.svg b/packages/grafana-icons/svg/text-fields.svg deleted file mode 100644 index edce4350562..00000000000 --- a/packages/grafana-icons/svg/text-fields.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/thumbs-up.svg b/packages/grafana-icons/svg/thumbs-up.svg deleted file mode 100644 index acd0bf3d1bb..00000000000 --- a/packages/grafana-icons/svg/thumbs-up.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/times-circle.svg b/packages/grafana-icons/svg/times-circle.svg deleted file mode 100644 index cef7d52cce1..00000000000 --- a/packages/grafana-icons/svg/times-circle.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/times.svg b/packages/grafana-icons/svg/times.svg deleted file mode 100644 index 11e673b5f78..00000000000 --- a/packages/grafana-icons/svg/times.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/toggle-off.svg b/packages/grafana-icons/svg/toggle-off.svg deleted file mode 100644 index 694299f7c41..00000000000 --- a/packages/grafana-icons/svg/toggle-off.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/toggle-on.svg b/packages/grafana-icons/svg/toggle-on.svg deleted file mode 100644 index 046e640849d..00000000000 --- a/packages/grafana-icons/svg/toggle-on.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/traces.svg b/packages/grafana-icons/svg/traces.svg deleted file mode 100644 index a58acc03951..00000000000 --- a/packages/grafana-icons/svg/traces.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/grafana-icons/svg/trash-alt.svg b/packages/grafana-icons/svg/trash-alt.svg deleted file mode 100644 index 54bef88545d..00000000000 --- a/packages/grafana-icons/svg/trash-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/unarchive.svg b/packages/grafana-icons/svg/unarchive.svg deleted file mode 100644 index afe7b426b38..00000000000 --- a/packages/grafana-icons/svg/unarchive.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/grafana-icons/svg/unlock.svg b/packages/grafana-icons/svg/unlock.svg deleted file mode 100644 index 67864413a35..00000000000 --- a/packages/grafana-icons/svg/unlock.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/upload.svg b/packages/grafana-icons/svg/upload.svg deleted file mode 100644 index ac85cdff001..00000000000 --- a/packages/grafana-icons/svg/upload.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/user-arrows.svg b/packages/grafana-icons/svg/user-arrows.svg deleted file mode 100644 index 91612a70103..00000000000 --- a/packages/grafana-icons/svg/user-arrows.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/user.svg b/packages/grafana-icons/svg/user.svg deleted file mode 100644 index 2236c900d99..00000000000 --- a/packages/grafana-icons/svg/user.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/users-alt.svg b/packages/grafana-icons/svg/users-alt.svg deleted file mode 100644 index 93184b9dcb6..00000000000 --- a/packages/grafana-icons/svg/users-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/vertical-align-bottom.svg b/packages/grafana-icons/svg/vertical-align-bottom.svg deleted file mode 100644 index de486c4f1c2..00000000000 --- a/packages/grafana-icons/svg/vertical-align-bottom.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/vertical-align-center.svg b/packages/grafana-icons/svg/vertical-align-center.svg deleted file mode 100644 index 0720428e4e3..00000000000 --- a/packages/grafana-icons/svg/vertical-align-center.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/vertical-align-top.svg b/packages/grafana-icons/svg/vertical-align-top.svg deleted file mode 100644 index d2a5d96169e..00000000000 --- a/packages/grafana-icons/svg/vertical-align-top.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/web-section-alt.svg b/packages/grafana-icons/svg/web-section-alt.svg deleted file mode 100644 index 2ac16c2db81..00000000000 --- a/packages/grafana-icons/svg/web-section-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/wrap-text.svg b/packages/grafana-icons/svg/wrap-text.svg deleted file mode 100644 index 5836bc2e233..00000000000 --- a/packages/grafana-icons/svg/wrap-text.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/svg/x.svg b/packages/grafana-icons/svg/x.svg deleted file mode 100644 index 56e7e84f16a..00000000000 --- a/packages/grafana-icons/svg/x.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/grafana-icons/templates/icon.cjs b/packages/grafana-icons/templates/icon.cjs deleted file mode 100644 index 5fd12b10508..00000000000 --- a/packages/grafana-icons/templates/icon.cjs +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Modify the JSX to use the IconBase component as a wrapper - */ -const modifyJSX = (jsx) => { - jsx.openingElement.name.name = 'IconBase'; - jsx.openingElement.attributes = [ - ...jsx.openingElement.attributes, - { - type: 'JSXSpreadAttribute', - argument: { - type: 'Identifier', - name: 'props', - }, - }, - ]; - - jsx.closingElement.name.name = 'IconBase'; - - return jsx; -}; - -const comments = ` -// This is an auto-generated file, created by svgr-cli. -// Do not edit this file manually. -// To update the component, modify the template in templates/icon.js. -// Run "yarn generate" to update. -`; -const imports = ` -import { memo } from 'react'; - -import { IconBase, IconProps } from '../IconBase'; -`; -const template = ({ exports, jsx, componentName }, { tpl }) => { - return tpl` -${comments} -${imports} - -const ${componentName} = (props: IconProps) => ( - ${modifyJSX(jsx)} -); - -${exports}; -`; -}; - -module.exports = template; diff --git a/packages/grafana-icons/templates/index.cjs b/packages/grafana-icons/templates/index.cjs deleted file mode 100644 index 9bbd0507a39..00000000000 --- a/packages/grafana-icons/templates/index.cjs +++ /dev/null @@ -1,12 +0,0 @@ -const path = require('path'); - -function defaultIndexTemplate(filePaths) { - const exportEntries = filePaths.map(({ path: filePath }) => { - const basename = path.basename(filePath, path.extname(filePath)); - const exportName = /^\d/.test(basename) ? `Svg${basename}` : basename; - return `export { default as ${exportName} } from './icons-gen/${basename}'`; - }); - return ["export { type IconProps } from './IconBase';", ...exportEntries].join('\n'); -} - -module.exports = defaultIndexTemplate; diff --git a/packages/grafana-icons/tsconfig.build.json b/packages/grafana-icons/tsconfig.build.json deleted file mode 100644 index 54309163ebc..00000000000 --- a/packages/grafana-icons/tsconfig.build.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "exclude": ["dist", "node_modules", "test", "**/*.test.ts*"], - "extends": "./tsconfig.json" -} diff --git a/packages/grafana-icons/tsconfig.json b/packages/grafana-icons/tsconfig.json deleted file mode 100644 index bf218cdd730..00000000000 --- a/packages/grafana-icons/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "jsx": "react-jsx", - "baseUrl": "./", - "declarationDir": "./compiled", - "emitDeclarationOnly": true, - "isolatedModules": true, - "rootDirs": ["."], - "resolveJsonModule": true - }, - "exclude": ["dist/**/*"], - "extends": "@grafana/tsconfig", - "include": ["src/**/*.tsx"] -} diff --git a/project.json b/project.json index 9c2c25d1e8d..b67053dbbe6 100644 --- a/project.json +++ b/project.json @@ -6,7 +6,6 @@ "start": { "dependsOn": [ "themes-generate", - "grafana-icons:generate", { "projects": ["tag:scope:plugin"], "target": "build" @@ -16,7 +15,6 @@ "build": { "dependsOn": [ "themes-generate", - "grafana-icons:generate", { "projects": ["tag:scope:plugin"], "target": "build" diff --git a/yarn.lock b/yarn.lock index 42dd66568b5..f41e045bbf0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -99,7 +99,7 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:7.26.10, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.9, @babel/core@npm:^7.21.3, @babel/core@npm:^7.22.9": +"@babel/core@npm:7.26.10, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.9, @babel/core@npm:^7.22.9": version: 7.26.10 resolution: "@babel/core@npm:7.26.10" dependencies: @@ -1451,7 +1451,7 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.5, @babel/types@npm:^7.24.7, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.10, @babel/types@npm:^7.27.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.5, @babel/types@npm:^7.24.7, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.10, @babel/types@npm:^7.27.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": version: 7.27.0 resolution: "@babel/types@npm:7.27.0" dependencies: @@ -3439,41 +3439,6 @@ __metadata: languageName: unknown linkType: soft -"@grafana/saga-icons@workspace:*, @grafana/saga-icons@workspace:packages/grafana-icons": - version: 0.0.0-use.local - resolution: "@grafana/saga-icons@workspace:packages/grafana-icons" - dependencies: - "@babel/core": "npm:7.26.10" - "@grafana/tsconfig": "npm:^2.0.0" - "@rollup/plugin-node-resolve": "npm:^16.0.0" - "@rollup/plugin-typescript": "npm:^12.1.0" - "@svgr/babel-plugin-remove-jsx-attribute": "npm:^8.0.0" - "@svgr/cli": "npm:^8.1.0" - "@svgr/core": "npm:8.1.0" - "@svgr/plugin-jsx": "npm:^8.1.0" - "@svgr/plugin-prettier": "npm:^8.1.0" - "@svgr/plugin-svgo": "npm:^8.1.0" - "@types/babel__core": "npm:^7" - "@types/node": "npm:22.12.0" - "@types/react": "npm:18.3.18" - "@types/react-dom": "npm:18.3.5" - esbuild: "npm:0.25.0" - prettier: "npm:3.4.2" - react: "npm:18.3.1" - react-dom: "npm:18.3.1" - rimraf: "npm:6.0.1" - rollup: "npm:^4.22.4" - rollup-plugin-dts: "npm:^6.1.1" - rollup-plugin-esbuild: "npm:6.2.0" - rollup-plugin-node-externals: "npm:8.0.0" - ts-node: "npm:10.9.2" - typescript: "npm:5.7.3" - peerDependencies: - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - languageName: unknown - linkType: soft - "@grafana/scenes-react@npm:^6.8.1": version: 6.9.0 resolution: "@grafana/scenes-react@npm:6.9.0" @@ -6561,7 +6526,7 @@ __metadata: languageName: node linkType: hard -"@rollup/plugin-node-resolve@npm:16.0.0, @rollup/plugin-node-resolve@npm:^16.0.0": +"@rollup/plugin-node-resolve@npm:16.0.0": version: 16.0.0 resolution: "@rollup/plugin-node-resolve@npm:16.0.0" dependencies: @@ -6595,25 +6560,6 @@ __metadata: languageName: node linkType: hard -"@rollup/plugin-typescript@npm:^12.1.0": - version: 12.1.1 - resolution: "@rollup/plugin-typescript@npm:12.1.1" - dependencies: - "@rollup/pluginutils": "npm:^5.1.0" - resolve: "npm:^1.22.1" - peerDependencies: - rollup: ^2.14.0||^3.0.0||^4.0.0 - tslib: "*" - typescript: ">=3.7.0" - peerDependenciesMeta: - rollup: - optional: true - tslib: - optional: true - checksum: 10/838d5e67d1b383154fab7ae1b0c58e91844c70380210b12c1d5f2ed5d2264d4fbd21ff991a13a4a72078dce897b5c482c70554a21671269219aa9d2525f14dcd - languageName: node - linkType: hard - "@rollup/pluginutils@npm:^3.0.9": version: 3.1.0 resolution: "@rollup/pluginutils@npm:3.1.0" @@ -6627,7 +6573,7 @@ __metadata: languageName: node linkType: hard -"@rollup/pluginutils@npm:^5.0.1, @rollup/pluginutils@npm:^5.1.0": +"@rollup/pluginutils@npm:^5.0.1": version: 5.1.2 resolution: "@rollup/pluginutils@npm:5.1.2" dependencies: @@ -7600,178 +7546,6 @@ __metadata: languageName: node linkType: hard -"@svgr/babel-plugin-add-jsx-attribute@npm:8.0.0": - version: 8.0.0 - resolution: "@svgr/babel-plugin-add-jsx-attribute@npm:8.0.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/3fc8e35d16f5abe0af5efe5851f27581225ac405d6a1ca44cda0df064cddfcc29a428c48c2e4bef6cebf627c9ac2f652a096030edb02cf5a120ce28d3c234710 - languageName: node - linkType: hard - -"@svgr/babel-plugin-remove-jsx-attribute@npm:8.0.0, @svgr/babel-plugin-remove-jsx-attribute@npm:^8.0.0": - version: 8.0.0 - resolution: "@svgr/babel-plugin-remove-jsx-attribute@npm:8.0.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/ff992893c6c4ac802713ba3a97c13be34e62e6d981c813af40daabcd676df68a72a61bd1e692bb1eda3587f1b1d700ea462222ae2153bb0f46886632d4f88d08 - languageName: node - linkType: hard - -"@svgr/babel-plugin-remove-jsx-empty-expression@npm:8.0.0": - version: 8.0.0 - resolution: "@svgr/babel-plugin-remove-jsx-empty-expression@npm:8.0.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/0fb691b63a21bac00da3aa2dccec50d0d5a5b347ff408d60803b84410d8af168f2656e4ba1ee1f24dab0ae4e4af77901f2928752bb0434c1f6788133ec599ec8 - languageName: node - linkType: hard - -"@svgr/babel-plugin-replace-jsx-attribute-value@npm:8.0.0": - version: 8.0.0 - resolution: "@svgr/babel-plugin-replace-jsx-attribute-value@npm:8.0.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/1edda65ef4f4dd8f021143c8ec276a08f6baa6f733b8e8ee2e7775597bf6b97afb47fdeefd579d6ae6c959fe2e634f55cd61d99377631212228c8cfb351b8921 - languageName: node - linkType: hard - -"@svgr/babel-plugin-svg-dynamic-title@npm:8.0.0": - version: 8.0.0 - resolution: "@svgr/babel-plugin-svg-dynamic-title@npm:8.0.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/876cec891488992e6a9aebb8155e2bea4ec461b4718c51de36e988e00e271c6d9d01ef6be17b9effd44b2b3d7db0b41c161a5904a46ae6f38b26b387ad7f3709 - languageName: node - linkType: hard - -"@svgr/babel-plugin-svg-em-dimensions@npm:8.0.0": - version: 8.0.0 - resolution: "@svgr/babel-plugin-svg-em-dimensions@npm:8.0.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/be0e2d391164428327d9ec469a52cea7d93189c6b0e2c290999e048f597d777852f701c64dca44cd45b31ed14a7f859520326e2e4ad7c3a4545d0aa235bc7e9a - languageName: node - linkType: hard - -"@svgr/babel-plugin-transform-react-native-svg@npm:8.1.0": - version: 8.1.0 - resolution: "@svgr/babel-plugin-transform-react-native-svg@npm:8.1.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/85b434a57572f53bd2b9f0606f253e1fcf57b4a8c554ec3f2d43ed17f50d8cae200cb3aaf1ec9d626e1456e8b135dce530ae047eb0bed6d4bf98a752d6640459 - languageName: node - linkType: hard - -"@svgr/babel-plugin-transform-svg-component@npm:8.0.0": - version: 8.0.0 - resolution: "@svgr/babel-plugin-transform-svg-component@npm:8.0.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/86ca139c0be0e7df05f103c5f10874387ada1434ca0286584ba9cd367c259d74bf9c86700b856449f46cf674bd6f0cf18f8f034f6d3f0e2ce5e5435c25dbff4b - languageName: node - linkType: hard - -"@svgr/babel-preset@npm:8.1.0": - version: 8.1.0 - resolution: "@svgr/babel-preset@npm:8.1.0" - dependencies: - "@svgr/babel-plugin-add-jsx-attribute": "npm:8.0.0" - "@svgr/babel-plugin-remove-jsx-attribute": "npm:8.0.0" - "@svgr/babel-plugin-remove-jsx-empty-expression": "npm:8.0.0" - "@svgr/babel-plugin-replace-jsx-attribute-value": "npm:8.0.0" - "@svgr/babel-plugin-svg-dynamic-title": "npm:8.0.0" - "@svgr/babel-plugin-svg-em-dimensions": "npm:8.0.0" - "@svgr/babel-plugin-transform-react-native-svg": "npm:8.1.0" - "@svgr/babel-plugin-transform-svg-component": "npm:8.0.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/3a67930f080b8891e1e8e2595716b879c944d253112bae763dce59807ba23454d162216c8d66a0a0e3d4f38a649ecd6c387e545d1e1261dd69a68e9a3392ee08 - languageName: node - linkType: hard - -"@svgr/cli@npm:^8.1.0": - version: 8.1.0 - resolution: "@svgr/cli@npm:8.1.0" - dependencies: - "@svgr/core": "npm:8.1.0" - "@svgr/plugin-jsx": "npm:8.1.0" - "@svgr/plugin-prettier": "npm:8.1.0" - "@svgr/plugin-svgo": "npm:8.1.0" - camelcase: "npm:^6.2.0" - chalk: "npm:^4.1.2" - commander: "npm:^9.4.1" - dashify: "npm:^2.0.0" - glob: "npm:^8.0.3" - snake-case: "npm:^3.0.4" - bin: - svgr: bin/svgr - checksum: 10/b3478ebcbc3783160b548af5bfe352decdf6255b54d9256b752b2fabc296d743d55805eb596b82110e482b176fc3eab9ad91f979e92a505f224b27c1b9e3dbb1 - languageName: node - linkType: hard - -"@svgr/core@npm:8.1.0": - version: 8.1.0 - resolution: "@svgr/core@npm:8.1.0" - dependencies: - "@babel/core": "npm:^7.21.3" - "@svgr/babel-preset": "npm:8.1.0" - camelcase: "npm:^6.2.0" - cosmiconfig: "npm:^8.1.3" - snake-case: "npm:^3.0.4" - checksum: 10/bc98cd5fc349ab9dcf0c13c2279164726d45878cdac8999090765379c6e897a1b24aca641c12a3c33f578d06f7a09252fb090962a4695c753fb02b627a56bfe6 - languageName: node - linkType: hard - -"@svgr/hast-util-to-babel-ast@npm:8.0.0": - version: 8.0.0 - resolution: "@svgr/hast-util-to-babel-ast@npm:8.0.0" - dependencies: - "@babel/types": "npm:^7.21.3" - entities: "npm:^4.4.0" - checksum: 10/243aa9c92d66aa3f1fc82851fe1fa376808a08fcc02719fed38ebfb4e25cf3e3c1282c185300c29953d047c36acb9e3ac588d46b0af55a3b7a5186a6badec8a9 - languageName: node - linkType: hard - -"@svgr/plugin-jsx@npm:8.1.0, @svgr/plugin-jsx@npm:^8.1.0": - version: 8.1.0 - resolution: "@svgr/plugin-jsx@npm:8.1.0" - dependencies: - "@babel/core": "npm:^7.21.3" - "@svgr/babel-preset": "npm:8.1.0" - "@svgr/hast-util-to-babel-ast": "npm:8.0.0" - svg-parser: "npm:^2.0.4" - peerDependencies: - "@svgr/core": "*" - checksum: 10/0418a9780753d3544912ee2dad5d2cf8d12e1ba74df8053651b3886aeda54d5f0f7d2dece0af5e0d838332c4f139a57f0dabaa3ca1afa4d1a765efce6a7656f2 - languageName: node - linkType: hard - -"@svgr/plugin-prettier@npm:8.1.0, @svgr/plugin-prettier@npm:^8.1.0": - version: 8.1.0 - resolution: "@svgr/plugin-prettier@npm:8.1.0" - dependencies: - deepmerge: "npm:^4.3.1" - prettier: "npm:^2.8.7" - peerDependencies: - "@svgr/core": "*" - checksum: 10/834373d7d34906cfa67191933153c8c71bb5c13eff71e113730c8642b2abcce136cb0e1869d52642fe305593a84c1d54d39543f24091f6b8a2620c1333691638 - languageName: node - linkType: hard - -"@svgr/plugin-svgo@npm:8.1.0, @svgr/plugin-svgo@npm:^8.1.0": - version: 8.1.0 - resolution: "@svgr/plugin-svgo@npm:8.1.0" - dependencies: - cosmiconfig: "npm:^8.1.3" - deepmerge: "npm:^4.3.1" - svgo: "npm:^3.0.2" - peerDependencies: - "@svgr/core": "*" - checksum: 10/59d9d214cebaacca9ca71a561f463d8b7e5a68ca9443e4792a42d903acd52259b1790c0680bc6afecc3f00a255a6cbd7ea278a9f625bac443620ea58a590c2d0 - languageName: node - linkType: hard - "@swagger-api/apidom-ast@npm:^1.0.0-beta.30": version: 1.0.0-beta.30 resolution: "@swagger-api/apidom-ast@npm:1.0.0-beta.30" @@ -13066,13 +12840,6 @@ __metadata: languageName: node linkType: hard -"commander@npm:^9.4.1": - version: 9.5.0 - resolution: "commander@npm:9.5.0" - checksum: 10/41c49b3d0f94a1fbeb0463c85b13f15aa15a9e0b4d5e10a49c0a1d58d4489b549d62262b052ae0aa6cfda53299bee487bfe337825df15e342114dde543f82906 - languageName: node - linkType: hard - "comment-parser@npm:1.4.1": version: 1.4.1 resolution: "comment-parser@npm:1.4.1" @@ -13477,7 +13244,7 @@ __metadata: languageName: node linkType: hard -"cosmiconfig@npm:^8.1.3, cosmiconfig@npm:^8.2.0": +"cosmiconfig@npm:^8.2.0": version: 8.3.6 resolution: "cosmiconfig@npm:8.3.6" dependencies: @@ -14387,13 +14154,6 @@ __metadata: languageName: node linkType: hard -"dashify@npm:^2.0.0": - version: 2.0.0 - resolution: "dashify@npm:2.0.0" - checksum: 10/f13233f38fc39ea8dabe3a5bef51421906aa8a52e4403fcb56e3e6464428f5c0bdd75562706929a245c698bceccf68a82e30e9e327a0c582469868522a163f8c - languageName: node - linkType: hard - "data-urls@npm:^3.0.2": version: 3.0.2 resolution: "data-urls@npm:3.0.2" @@ -17441,19 +17201,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:^8.0.3": - version: 8.1.0 - resolution: "glob@npm:8.1.0" - dependencies: - fs.realpath: "npm:^1.0.0" - inflight: "npm:^1.0.4" - inherits: "npm:2" - minimatch: "npm:^5.0.1" - once: "npm:^1.3.0" - checksum: 10/9aab1c75eb087c35dbc41d1f742e51d0507aa2b14c910d96fb8287107a10a22f4bbdce26fc0a3da4c69a20f7b26d62f1640b346a4f6e6becfff47f335bb1dc5e - languageName: node - linkType: hard - "glob@npm:^9.2.0": version: 9.3.5 resolution: "glob@npm:9.3.5" @@ -17629,7 +17376,6 @@ __metadata: "@grafana/plugin-ui": "npm:0.10.5" "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" - "@grafana/saga-icons": "workspace:*" "@grafana/scenes": "npm:^6.8.1" "@grafana/scenes-react": "npm:^6.8.1" "@grafana/schema": "workspace:*" @@ -24635,7 +24381,7 @@ __metadata: languageName: node linkType: hard -"prettier@npm:^2.3.2, prettier@npm:^2.8.7": +"prettier@npm:^2.3.2": version: 2.8.8 resolution: "prettier@npm:2.8.8" bin: @@ -27060,7 +26806,7 @@ __metadata: languageName: node linkType: hard -"rollup-plugin-node-externals@npm:8.0.0, rollup-plugin-node-externals@npm:^8.0.0": +"rollup-plugin-node-externals@npm:^8.0.0": version: 8.0.0 resolution: "rollup-plugin-node-externals@npm:8.0.0" peerDependencies: @@ -28074,16 +27820,6 @@ __metadata: languageName: node linkType: hard -"snake-case@npm:^3.0.4": - version: 3.0.4 - resolution: "snake-case@npm:3.0.4" - dependencies: - dot-case: "npm:^3.0.4" - tslib: "npm:^2.0.3" - checksum: 10/0a7a79900bbb36f8aaa922cf111702a3647ac6165736d5dc96d3ef367efc50465cac70c53cd172c382b022dac72ec91710608e5393de71f76d7142e6fd80e8a3 - languageName: node - linkType: hard - "socket.io-adapter@npm:~2.5.2": version: 2.5.5 resolution: "socket.io-adapter@npm:2.5.5" @@ -29039,13 +28775,6 @@ __metadata: languageName: node linkType: hard -"svg-parser@npm:^2.0.4": - version: 2.0.4 - resolution: "svg-parser@npm:2.0.4" - checksum: 10/ec196da6ea21481868ab26911970e35488361c39ead1c6cdd977ba16c885c21a91ddcbfd113bfb01f79a822e2a751ef85b2f7f95e2cb9245558ebce12c34af1f - languageName: node - linkType: hard - "svg-tags@npm:^1.0.0": version: 1.0.0 resolution: "svg-tags@npm:1.0.0" @@ -29053,7 +28782,7 @@ __metadata: languageName: node linkType: hard -"svgo@npm:^3.0.2, svgo@npm:^3.3.2": +"svgo@npm:^3.3.2": version: 3.3.2 resolution: "svgo@npm:3.3.2" dependencies: From 82c291675bfbbbbce54c0fcd1d28e45bf3471967 Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Thu, 24 Apr 2025 09:29:41 +0300 Subject: [PATCH 073/146] Provisioning: Fix requires migration condition (#104434) * Provisioning: Fix requires migration condition * Better error messaging --- public/app/features/provisioning/Job/JobContent.tsx | 4 ++-- public/app/features/provisioning/Wizard/BootstrapStep.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/public/app/features/provisioning/Job/JobContent.tsx b/public/app/features/provisioning/Job/JobContent.tsx index c5f0ef5b76d..e41bad2502c 100644 --- a/public/app/features/provisioning/Job/JobContent.tsx +++ b/public/app/features/provisioning/Job/JobContent.tsx @@ -17,7 +17,7 @@ export function JobContent({ job, isFinishedJob = false }: JobContentProps) { return null; } - const { state, message, progress, summary } = job.status; + const { state, message, progress, summary, errors } = job.status; const repoName = job.metadata?.labels?.['provisioning.grafana.app/repository']; const getStatusDisplay = () => { @@ -35,7 +35,7 @@ export function JobContent({ job, isFinishedJob = false }: JobContentProps) { severity="error" title={t('provisioning.job-status.status.title-error-running-job', 'Error running job')} > - {message} + {message ?? errors?.join('\n')} ); } diff --git a/public/app/features/provisioning/Wizard/BootstrapStep.tsx b/public/app/features/provisioning/Wizard/BootstrapStep.tsx index f06e5764450..8bada6d5a57 100644 --- a/public/app/features/provisioning/Wizard/BootstrapStep.tsx +++ b/public/app/features/provisioning/Wizard/BootstrapStep.tsx @@ -57,7 +57,7 @@ export function BootstrapStep({ onOptionSelect, settingsData, repoName, onStepSt useEffect(() => { const { target } = options[0]; setValue('repository.sync.target', target); - onOptionSelect(target !== 'folder' || resourceCount > 0); + onOptionSelect(settingsData?.legacyStorage || resourceCount > 0); // Only run this effect on mount // eslint-disable-next-line react-hooks/exhaustive-deps }, []); From e6414a6690618b954a9653589a762b09aecd356b Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Thu, 24 Apr 2025 10:29:49 +0300 Subject: [PATCH 074/146] Provisioning: Fix the save version in dashboard scenes (#104433) fix save (correct v1beta1) --- public/app/features/dashboard-scene/scene/DashboardScene.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index 2a6d6abece9..29433b0bb56 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -775,7 +775,7 @@ export class DashboardScene extends SceneObjectBase impleme const { meta } = this.state; const spec = this.getSaveAsModel(options); - const apiVersion = this.serializer instanceof V2DashboardSerializer ? 'v2alpha1' : 'v1alpha1'; // get from the dashboard? + const apiVersion = this.serializer instanceof V2DashboardSerializer ? 'v2alpha1' : 'v1beta1'; // get from the dashboard? return { apiVersion: `dashboard.grafana.app/${apiVersion}`, kind: 'Dashboard', From faa33e30a9ebd14b9a3157f0e40d0054223f88cd Mon Sep 17 00:00:00 2001 From: Victor Marin <36818606+mdvictor@users.noreply.github.com> Date: Thu, 24 Apr 2025 11:13:17 +0300 Subject: [PATCH 075/146] Schema: Add origin property to AdHocFilterWithLabels (#104320) * add origin to AdHocFilterWithLabels * typo * fix * use singular for FilterOrigin * run ./hack/update-codegen.sh * add canary scenes to verify typechecks * update dashboard schema json snapshot * fix * fix * update canaries * fix * bump scenes version --- .../kinds/v2alpha1/dashboard_spec.cue | 5 ++++ .../v0alpha1/dashboard_object_gen.go | 2 ++ .../dashboard/v1beta1/dashboard_object_gen.go | 2 ++ .../dashboard/v2alpha1/dashboard_spec.cue | 5 ++++ .../dashboard/v2alpha1/dashboard_spec_gen.go | 25 +++++++++++++------ .../v2alpha1/zz_generated.openapi.go | 6 +++++ apps/dashboard/pkg/apis/dashboard_manifest.go | 2 ++ package.json | 4 +-- .../dashboard/v2alpha1/types.spec.gen.ts | 7 ++++++ .../dashboard.grafana.app-v2alpha1.json | 5 +++- yarn.lock | 22 ++++++++-------- 11 files changed, 64 insertions(+), 21 deletions(-) diff --git a/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue b/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue index 141e420b462..31bfcc723cf 100644 --- a/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue +++ b/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue @@ -704,6 +704,10 @@ VariableRefresh: *"never" | "onDashboardLoad" | "onTimeRangeChanged" // Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing). VariableHide: *"dontHide" | "hideLabel" | "hideVariable" +// Determine the origin of the adhoc variable filter +// Accepted values are `dashboard` (filter originated from dashboard), or `scope` (filter originated from scope). +FilterOrigin: "dashboard" | "scope" + // FIXME: should we introduce this? --- Variable value option VariableValueOption: { label: string @@ -915,6 +919,7 @@ AdHocFilterWithLabels: { keyLabel?: string valueLabels?: [...string] forceEdit?: bool + origin?: FilterOrigin // @deprecated condition?: string } diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go b/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go index a267e0c8df8..50847df87c3 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go @@ -294,6 +294,8 @@ var _ resource.ListObject = &DashboardList{} // Copy methods for all subresource types + + // DeepCopy creates a full deep copy of DashboardStatus func (s *DashboardStatus) DeepCopy() *DashboardStatus { cpy := &DashboardStatus{} diff --git a/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_object_gen.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_object_gen.go index be021b5f003..1423c7b0603 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_object_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_object_gen.go @@ -294,6 +294,8 @@ var _ resource.ListObject = &DashboardList{} // Copy methods for all subresource types + + // DeepCopy creates a full deep copy of DashboardStatus func (s *DashboardStatus) DeepCopy() *DashboardStatus { cpy := &DashboardStatus{} diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec.cue b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec.cue index c1acdf81a2f..a369449ea0e 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec.cue +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec.cue @@ -708,6 +708,10 @@ VariableRefresh: *"never" | "onDashboardLoad" | "onTimeRangeChanged" // Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing). VariableHide: *"dontHide" | "hideLabel" | "hideVariable" +// Determine the origin of the adhoc variable filter +// Accepted values are `dashboard` (filter originated from dashboard), or `scope` (filter originated from scope). +FilterOrigin: "dashboard" | "scope" + // FIXME: should we introduce this? --- Variable value option VariableValueOption: { label: string @@ -919,6 +923,7 @@ AdHocFilterWithLabels: { keyLabel?: string valueLabels?: [...string] forceEdit?: bool + origin?: FilterOrigin // @deprecated condition?: string } diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go index 70faef285ea..23d3c77fa76 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go @@ -1665,13 +1665,14 @@ func NewDashboardAdhocVariableSpec() *DashboardAdhocVariableSpec { // Define the AdHocFilterWithLabels type // +k8s:openapi-gen=true type DashboardAdHocFilterWithLabels struct { - Key string `json:"key"` - Operator string `json:"operator"` - Value string `json:"value"` - Values []string `json:"values,omitempty"` - KeyLabel *string `json:"keyLabel,omitempty"` - ValueLabels []string `json:"valueLabels,omitempty"` - ForceEdit *bool `json:"forceEdit,omitempty"` + Key string `json:"key"` + Operator string `json:"operator"` + Value string `json:"value"` + Values []string `json:"values,omitempty"` + KeyLabel *string `json:"keyLabel,omitempty"` + ValueLabels []string `json:"valueLabels,omitempty"` + ForceEdit *bool `json:"forceEdit,omitempty"` + Origin *DashboardFilterOrigin `json:"origin,omitempty"` // @deprecated Condition *string `json:"condition,omitempty"` } @@ -1681,6 +1682,16 @@ func NewDashboardAdHocFilterWithLabels() *DashboardAdHocFilterWithLabels { return &DashboardAdHocFilterWithLabels{} } +// Determine the origin of the adhoc variable filter +// Accepted values are `dashboard` (filter originated from dashboard), or `scope` (filter originated from scope). +// +k8s:openapi-gen=true +type DashboardFilterOrigin string + +const ( + DashboardFilterOriginDashboard DashboardFilterOrigin = "dashboard" + DashboardFilterOriginScope DashboardFilterOrigin = "scope" +) + // Define the MetricFindValue type // +k8s:openapi-gen=true type DashboardMetricFindValue struct { diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go index 4f548196584..f777f9c4f82 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go @@ -380,6 +380,12 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardAdHocFilterWithLabels(ref commo Format: "", }, }, + "origin": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, "condition": { SchemaProps: spec.SchemaProps{ Description: "@deprecated", diff --git a/apps/dashboard/pkg/apis/dashboard_manifest.go b/apps/dashboard/pkg/apis/dashboard_manifest.go index cc5451a78e8..e8ebdfbc5ed 100644 --- a/apps/dashboard/pkg/apis/dashboard_manifest.go +++ b/apps/dashboard/pkg/apis/dashboard_manifest.go @@ -11,6 +11,8 @@ import ( "github.com/grafana/grafana-app-sdk/app" ) +var () + var appManifestData = app.ManifestData{ AppName: "dashboard", Group: "dashboard.grafana.app", diff --git a/package.json b/package.json index 1b0482f1c7c..226898e3351 100644 --- a/package.json +++ b/package.json @@ -274,8 +274,8 @@ "@grafana/plugin-ui": "0.10.5", "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "^6.8.1", - "@grafana/scenes-react": "^6.8.1", + "@grafana/scenes": "6.10.0", + "@grafana/scenes-react": "6.10.0", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha1/types.spec.gen.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha1/types.spec.gen.ts index 2c633337b01..cee005bb943 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha1/types.spec.gen.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha1/types.spec.gen.ts @@ -1344,6 +1344,7 @@ export interface AdHocFilterWithLabels { keyLabel?: string; valueLabels?: string[]; forceEdit?: boolean; + origin?: FilterOrigin; // @deprecated condition?: string; } @@ -1354,6 +1355,12 @@ export const defaultAdHocFilterWithLabels = (): AdHocFilterWithLabels => ({ value: "", }); +// Determine the origin of the adhoc variable filter +// Accepted values are `dashboard` (filter originated from dashboard), or `scope` (filter originated from scope). +export type FilterOrigin = "dashboard" | "scope"; + +export const defaultFilterOrigin = (): FilterOrigin => ("dashboard"); + // Define the MetricFindValue type export interface MetricFindValue { text: string; diff --git a/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v2alpha1.json b/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v2alpha1.json index 8892f4b21cc..77707413bb4 100644 --- a/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v2alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v2alpha1.json @@ -1104,6 +1104,9 @@ "type": "string", "default": "" } + }, + "origin": { + "type": "string" } } }, @@ -4585,4 +4588,4 @@ } } } -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index f41e045bbf0..cdfd94dfddf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3439,11 +3439,11 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes-react@npm:^6.8.1": - version: 6.9.0 - resolution: "@grafana/scenes-react@npm:6.9.0" +"@grafana/scenes-react@npm:6.10.0": + version: 6.10.0 + resolution: "@grafana/scenes-react@npm:6.10.0" dependencies: - "@grafana/scenes": "npm:6.9.0" + "@grafana/scenes": "npm:6.10.0" lru-cache: "npm:^10.2.2" react-use: "npm:^17.4.0" peerDependencies: @@ -3455,13 +3455,13 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/c7c3759671f4497653a586e9396b5f89ac869dd30c9fb3077734ff80353fd612e2857136edc95d71c06607575930145af8d6811a088c9afda0299925c09ca9c7 + checksum: 10/559de67feb4005173bc4a08c0c968a32e2e88fc362192825bdae0e5f3e83cf5e78bb4c2a373c66ac2f38a90333622e6623e372989910baf48121135d032291f1 languageName: node linkType: hard -"@grafana/scenes@npm:6.9.0, @grafana/scenes@npm:^6.8.1": - version: 6.9.0 - resolution: "@grafana/scenes@npm:6.9.0" +"@grafana/scenes@npm:6.10.0": + version: 6.10.0 + resolution: "@grafana/scenes@npm:6.10.0" dependencies: "@floating-ui/react": "npm:^0.26.16" "@leeoniya/ufuzzy": "npm:^1.0.16" @@ -3479,7 +3479,7 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/ff02465c53877ce07d11f842696d00b0eb0945ca58d2586664429b66578c7743fb5ac715541649b69d52b2de8eef035340aa2df4cd0e8961c3b782eee9001551 + checksum: 10/4f89f59374f6bd20fcd0afec7d236e4e1754f43a379348e704c0021952b241ece2d04134de8d668bc4d4fa286ebcf1c7874403c70004e85b1330bd5f76bb5423 languageName: node linkType: hard @@ -17376,8 +17376,8 @@ __metadata: "@grafana/plugin-ui": "npm:0.10.5" "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" - "@grafana/scenes": "npm:^6.8.1" - "@grafana/scenes-react": "npm:^6.8.1" + "@grafana/scenes": "npm:6.10.0" + "@grafana/scenes-react": "npm:6.10.0" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/tsconfig": "npm:^2.0.0" From bfcb0af93a053079f36120a8ceb8978909a1359b Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Thu, 24 Apr 2025 11:14:57 +0300 Subject: [PATCH 076/146] UnifiedStorage: enable unifiedStorageHistoryPruner by default (#104437) --- .../grafana-data/src/types/featureToggles.gen.ts | 1 + pkg/services/featuremgmt/registry.go | 3 ++- pkg/services/featuremgmt/toggles_gen.csv | 2 +- pkg/services/featuremgmt/toggles_gen.json | 12 ++++++++---- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 39b36fcfd76..2f86e020d67 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -958,6 +958,7 @@ export interface FeatureToggles { alertingMigrationUI?: boolean; /** * Enables the unified storage history pruner + * @default true */ unifiedStorageHistoryPruner?: boolean; /** diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index cba0275dcaa..5b45c8a17dd 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1646,10 +1646,11 @@ var ( { Name: "unifiedStorageHistoryPruner", Description: "Enables the unified storage history pruner", - Stage: FeatureStageExperimental, + Stage: FeatureStageGeneralAvailability, Owner: grafanaSearchAndStorageSquad, HideFromAdminPage: true, HideFromDocs: true, + Expression: "true", // will be removed soon }, { Name: "azureMonitorLogsBuilderEditor", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 9760fa856ff..879a3810a37 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -215,7 +215,7 @@ grafanaManagedRecordingRulesDatasources,experimental,@grafana/alerting-squad,fal infinityRunQueriesInParallel,privatePreview,@grafana/oss-big-tent,false,false,false inviteUserExperimental,experimental,@grafana/sharing-squad,false,false,true alertingMigrationUI,experimental,@grafana/alerting-squad,false,false,true -unifiedStorageHistoryPruner,experimental,@grafana/search-and-storage,false,false,false +unifiedStorageHistoryPruner,GA,@grafana/search-and-storage,false,false,false azureMonitorLogsBuilderEditor,preview,@grafana/partner-datasources,false,false,false localeFormatPreference,experimental,@grafana/grafana-frontend-platform,false,false,false unifiedStorageGrpcConnectionPool,experimental,@grafana/search-and-storage,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index ee03a1c27f8..7d130cc9df4 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2963,15 +2963,19 @@ { "metadata": { "name": "unifiedStorageHistoryPruner", - "resourceVersion": "1745339544057", - "creationTimestamp": "2025-03-17T10:36:38Z" + "resourceVersion": "1745478115689", + "creationTimestamp": "2025-03-17T10:36:38Z", + "annotations": { + "grafana.app/updatedTimestamp": "2025-04-24 07:01:55.689179 +0000 UTC" + } }, "spec": { "description": "Enables the unified storage history pruner", - "stage": "experimental", + "stage": "GA", "codeowner": "@grafana/search-and-storage", "hideFromAdminPage": true, - "hideFromDocs": true + "hideFromDocs": true, + "expression": "true" } }, { From 496eafdc6fb8704f7ed09b8ee6dc259eb9022959 Mon Sep 17 00:00:00 2001 From: "grafana-pr-automation[bot]" <140550294+grafana-pr-automation[bot]@users.noreply.github.com> Date: Thu, 24 Apr 2025 09:26:05 +0100 Subject: [PATCH 077/146] I18n: Download translations from Crowdin (#104430) New Crowdin translations by GitHub Action Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- public/locales/cs-CZ/grafana.json | 15 +++------------ public/locales/de-DE/grafana.json | 15 +++------------ public/locales/es-ES/grafana.json | 15 +++------------ public/locales/fr-FR/grafana.json | 15 +++------------ public/locales/hu-HU/grafana.json | 15 +++------------ public/locales/id-ID/grafana.json | 15 +++------------ public/locales/it-IT/grafana.json | 15 +++------------ public/locales/ja-JP/grafana.json | 15 +++------------ public/locales/ko-KR/grafana.json | 15 +++------------ public/locales/nl-NL/grafana.json | 15 +++------------ public/locales/pl-PL/grafana.json | 15 +++------------ public/locales/pt-BR/grafana.json | 15 +++------------ public/locales/pt-PT/grafana.json | 15 +++------------ public/locales/ru-RU/grafana.json | 15 +++------------ public/locales/sv-SE/grafana.json | 15 +++------------ public/locales/tr-TR/grafana.json | 15 +++------------ public/locales/zh-Hans/grafana.json | 15 +++------------ public/locales/zh-Hant/grafana.json | 15 +++------------ 18 files changed, 54 insertions(+), 216 deletions(-) diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index 5907fadf05d..24c09600bbd 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -418,7 +418,6 @@ "action-buttons": { "delete": "", "edit-yaml": "", - "save": "", "save-exit": "" }, "title-delete-rule": "" @@ -3209,6 +3208,7 @@ "edit-pane": { "elements": { "dashboard": "Nástěnka", + "local-variable": "", "multiple-elements": "", "multiple-elements-delete-text": "", "multiple-panels": "", @@ -4330,7 +4330,7 @@ "hide-time-picker": "Skrýt volič času", "now-delay-description": "Vyloučit nedávná data, která mohou být neúplná.", "now-delay-label": "Aktuální zpoždění", - "refresh-live-dashboards-description": "Průběžně překreslovat panely, kde časový rozsah odkazuje na „teď“", + "refresh-live-dashboards-description": "", "refresh-live-dashboards-label": "Obnovit živé nástěnky", "time-options-label": "Časové možnosti", "time-zone-label": "Časové pásmo", @@ -4780,9 +4780,6 @@ "logs-volumne-panel-list": { "body-no-logs-volume-available": "" }, - "make-datasource-setup": { - "aria-label-query": "" - }, "next": "", "next-prev-result": { "aria-label-next": "", @@ -4904,8 +4901,6 @@ "rich-history-utils": { "a-week-ago": "před týdnem", "days-ago": "před {{num}} dny", - "default-from": "teď až 1 h", - "default-to": "teď", "today": "dnes", "two-weeks-ago": "před dvěma týdny", "yesterday": "včera" @@ -8587,15 +8582,11 @@ "apply": "Použít časový rozsah", "aria-role": "Výběr časového rozsahu", "default-title": "Časové rozsahy", - "example": "Příklad: pro výběr časového rozsahu od doby před 10 minutami do současnosti", - "example-details": "Od: teď–10 m Do: teď", "example-title": "Příklad časových rozsahů", "from-label": "Od", "from-to": "{{timeOptionFrom}} do {{timeOptionTo}}", - "more-info": "Další informace najdete v <2>dokumentaci<1>.", - "specify": "Zadejte časový rozsah <1>", + "specify": "", "submit-button-label": "Tlačítko odeslání voliče času", - "supported-formats": "Podporované formáty: <1>teď-[číslice]s/m/h/d/w", "to-label": "Do" }, "zone": { diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index c4ec43a2333..ee7c94e1abe 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -410,7 +410,6 @@ "action-buttons": { "delete": "", "edit-yaml": "", - "save": "", "save-exit": "" }, "title-delete-rule": "" @@ -3173,6 +3172,7 @@ "edit-pane": { "elements": { "dashboard": "", + "local-variable": "", "multiple-elements": "", "multiple-elements-delete-text": "", "multiple-panels": "", @@ -4288,7 +4288,7 @@ "hide-time-picker": "Zeitauswahl ausblenden", "now-delay-description": "Ausschließen aktueller Daten, die unvollständig sein könnten.", "now-delay-label": "Jetzt Verzögerung", - "refresh-live-dashboards-description": "Leiste ständig neu zeichnen, in denen der Zeitbereich „jetzt“ referenziert", + "refresh-live-dashboards-description": "", "refresh-live-dashboards-label": "Live-Dashboards aktualisieren", "time-options-label": "Zeitoptionen", "time-zone-label": "Zeitzone", @@ -4738,9 +4738,6 @@ "logs-volumne-panel-list": { "body-no-logs-volume-available": "" }, - "make-datasource-setup": { - "aria-label-query": "" - }, "next": "", "next-prev-result": { "aria-label-next": "", @@ -4862,8 +4859,6 @@ "rich-history-utils": { "a-week-ago": "vor einer Woche", "days-ago": "vor {{num}} Tagen", - "default-from": "jetzt bis 1 Stunde", - "default-to": "jetzt", "today": "heute", "two-weeks-ago": "vor zwei Wochen", "yesterday": "gestern" @@ -8525,15 +8520,11 @@ "apply": "", "aria-role": "Zeitbereichauswahl", "default-title": "Zeitbereiche", - "example": "Beispiel: So wählen Sie einen Zeitbereich von vor 10 Minuten bis jetzt aus", - "example-details": "Von: now-10m Bis: jetzt", "example-title": "Zeitbereiche-Beispiel", "from-label": "", "from-to": "{{timeOptionFrom}} bis {{timeOptionTo}}", - "more-info": "Weitere Informationen finden Sie in der <2>Dokumentation<1>.", - "specify": "Zeitbereich festlegen <1>", + "specify": "", "submit-button-label": "TimePicker-Schaltfläche „Absenden“", - "supported-formats": "Unterstützte Formate: <1>now-[digit]s/m/h/d/w", "to-label": "" }, "zone": { diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index 00d8392d91b..9f6e982821d 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -410,7 +410,6 @@ "action-buttons": { "delete": "", "edit-yaml": "", - "save": "", "save-exit": "" }, "title-delete-rule": "" @@ -3173,6 +3172,7 @@ "edit-pane": { "elements": { "dashboard": "", + "local-variable": "", "multiple-elements": "", "multiple-elements-delete-text": "", "multiple-panels": "", @@ -4288,7 +4288,7 @@ "hide-time-picker": "Ocultar el selector de tiempo", "now-delay-description": "Excluir los datos recientes que puedan estar incompletos.", "now-delay-label": "Retraso ahora", - "refresh-live-dashboards-description": "Redibujar continuamente los paneles en los que el rango de tiempo hace referencia a «ahora»", + "refresh-live-dashboards-description": "", "refresh-live-dashboards-label": "Actualizar paneles de control activos", "time-options-label": "Opciones de tiempo", "time-zone-label": "Huso horario", @@ -4738,9 +4738,6 @@ "logs-volumne-panel-list": { "body-no-logs-volume-available": "" }, - "make-datasource-setup": { - "aria-label-query": "" - }, "next": "", "next-prev-result": { "aria-label-next": "", @@ -4862,8 +4859,6 @@ "rich-history-utils": { "a-week-ago": "hace una semana", "days-ago": "hace {{num}} días", - "default-from": "ahora-1 h", - "default-to": "ahora", "today": "hoy", "two-weeks-ago": "hace dos semanas", "yesterday": "ayer" @@ -8525,15 +8520,11 @@ "apply": "", "aria-role": "Selección de intervalo de tiempo", "default-title": "Intervalos de tiempo", - "example": "Ejemplo: para seleccionar un intervalo de tiempo desde hace 10 minutos hasta ahora", - "example-details": "Desde: ahora-10 m Hasta: ahora", "example-title": "Ejemplos de intervalos de tiempo", "from-label": "", "from-to": "Desde {{timeOptionFrom}} hasta {{timeOptionTo}}", - "more-info": "Para obtener más información, consulta los <2>documentos <1>.", - "specify": "Especificar el intervalo de tiempo <1>", + "specify": "", "submit-button-label": "Botón de envío de selector de tiempo", - "supported-formats": "Formatos admitidos: <1>ahora-[dígito]s/m/h/d/w", "to-label": "" }, "zone": { diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 3e8fe7b3026..379c2d5b74a 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -410,7 +410,6 @@ "action-buttons": { "delete": "", "edit-yaml": "", - "save": "", "save-exit": "" }, "title-delete-rule": "" @@ -3173,6 +3172,7 @@ "edit-pane": { "elements": { "dashboard": "Tableau de bord", + "local-variable": "", "multiple-elements": "", "multiple-elements-delete-text": "", "multiple-panels": "", @@ -4288,7 +4288,7 @@ "hide-time-picker": "Cacher le sélecteur de temps", "now-delay-description": "Exclure les données récentes qui peuvent être incomplètes.", "now-delay-label": "Délai actuel", - "refresh-live-dashboards-description": "Redessiner en continu les panneaux où la plage de temps indique « maintenant »", + "refresh-live-dashboards-description": "", "refresh-live-dashboards-label": "Actualiser les tableaux de bord en direct", "time-options-label": "Options de temps", "time-zone-label": "Fuseau horaire", @@ -4738,9 +4738,6 @@ "logs-volumne-panel-list": { "body-no-logs-volume-available": "" }, - "make-datasource-setup": { - "aria-label-query": "" - }, "next": "", "next-prev-result": { "aria-label-next": "", @@ -4862,8 +4859,6 @@ "rich-history-utils": { "a-week-ago": "il y a une semaine", "days-ago": "il y a {{num}} jours", - "default-from": "il y a 1 h", - "default-to": "maintenant", "today": "aujourd'hui", "two-weeks-ago": "il y a deux semaines", "yesterday": "hier" @@ -8525,15 +8520,11 @@ "apply": "Appliquer la plage temporelle", "aria-role": "Sélection de la plage de temps", "default-title": "Plages de temps", - "example": "Exemple : pour sélectionner une plage temporelle allant de il y a 10 minutes à maintenant", - "example-details": "De : maintenant-10m À : maintenant", "example-title": "Exemple de plages de temps", "from-label": "De", "from-to": "{{timeOptionFrom}} à {{timeOptionTo}}", - "more-info": "Pour en savoir plus, consultez les <2>docs <1>.", - "specify": "Spécifiez la plage de temps <1>", + "specify": "", "submit-button-label": "Bouton d'envoi TimePicker", - "supported-formats": "Formats pris en charge : <1>maintenant-[chiffre]s/m/h/d/w", "to-label": "À" }, "zone": { diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index 49fadaec893..b45d05edbaf 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -410,7 +410,6 @@ "action-buttons": { "delete": "", "edit-yaml": "", - "save": "", "save-exit": "" }, "title-delete-rule": "" @@ -3173,6 +3172,7 @@ "edit-pane": { "elements": { "dashboard": "Irányítópult", + "local-variable": "", "multiple-elements": "", "multiple-elements-delete-text": "", "multiple-panels": "", @@ -4288,7 +4288,7 @@ "hide-time-picker": "Időválasztó elrejtése", "now-delay-description": "Kizárja azokat a legutóbbi adatokat, amelyek hiányosak lehetnek.", "now-delay-label": "Most késleltetése", - "refresh-live-dashboards-description": "Folyamatosan újrarajzolja azokat a paneleket, ahol az időtartomány a „most” értékre hivatkozik", + "refresh-live-dashboards-description": "", "refresh-live-dashboards-label": "Élő irányítópultok frissítése", "time-options-label": "Időbeállítások", "time-zone-label": "Időzóna", @@ -4738,9 +4738,6 @@ "logs-volumne-panel-list": { "body-no-logs-volume-available": "" }, - "make-datasource-setup": { - "aria-label-query": "" - }, "next": "", "next-prev-result": { "aria-label-next": "", @@ -4862,8 +4859,6 @@ "rich-history-utils": { "a-week-ago": "egy hete", "days-ago": "{{num}} napja", - "default-from": "most – 1 óra", - "default-to": "most", "today": "ma", "two-weeks-ago": "két héttel ezelőtt", "yesterday": "tegnap" @@ -8525,15 +8520,11 @@ "apply": "Időtartomány alkalmazása", "aria-role": "Időtartomány kijelölése", "default-title": "Időtartományok", - "example": "Példa: időtartomány kijelölése 10 perccel ezelőttől mostanáig", - "example-details": "Kezdés: now-10m Befejezés: now", "example-title": "Példa időtartományokra", "from-label": "Kezdete", "from-to": "{{timeOptionFrom}} – {{timeOptionTo}}", - "more-info": "További információért lásd a <2>dokumentációt<1>.", - "specify": "Adja meg az időtartományt <1>", + "specify": "", "submit-button-label": "TimePicker küldés gombja", - "supported-formats": "Támogatott formátumok: <1>now-[számjegyek]s/m/h/d/w", "to-label": "Vége" }, "zone": { diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index d6800eb23b0..36758020048 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -406,7 +406,6 @@ "action-buttons": { "delete": "", "edit-yaml": "", - "save": "", "save-exit": "" }, "title-delete-rule": "" @@ -3155,6 +3154,7 @@ "edit-pane": { "elements": { "dashboard": "Dasbor", + "local-variable": "", "multiple-elements": "", "multiple-elements-delete-text": "", "multiple-panels": "", @@ -4267,7 +4267,7 @@ "hide-time-picker": "Sembunyikan pemilih waktu", "now-delay-description": "Kecualikan data terbaru yang mungkin tidak lengkap.", "now-delay-label": "Sekarang tunda", - "refresh-live-dashboards-description": "Terus menggambar ulang panel ketika rentang waktu mengacu pada 'sekarang'", + "refresh-live-dashboards-description": "", "refresh-live-dashboards-label": "Muat ulang dasbor langsung", "time-options-label": "Opsi waktu", "time-zone-label": "Zona waktu", @@ -4717,9 +4717,6 @@ "logs-volumne-panel-list": { "body-no-logs-volume-available": "" }, - "make-datasource-setup": { - "aria-label-query": "" - }, "next": "", "next-prev-result": { "aria-label-next": "", @@ -4841,8 +4838,6 @@ "rich-history-utils": { "a-week-ago": "seminggu yang lalu", "days-ago": "{{num}} hari yang lalu", - "default-from": "sekarang—1 jam", - "default-to": "sekarang", "today": "hari ini", "two-weeks-ago": "dua minggu yang lalu", "yesterday": "kemarin" @@ -8494,15 +8489,11 @@ "apply": "Gunakan rentang waktu", "aria-role": "Pemilihan rentang waktu", "default-title": "Rentang waktu", - "example": "Contoh: untuk memilih rentang waktu dari 10 menit yang lalu hingga sekarang", - "example-details": "Dari: sekarang-10m Ke: sekarang", "example-title": "Contoh rentang waktu", "from-label": "Dari", "from-to": " {{timeOptionFrom}} hingga{{timeOptionTo}} ", - "more-info": "Untuk informasi selengkapnya lihat <2>dokumen <1>.", - "specify": "Sebutkan rentang waktu <1>", + "specify": "", "submit-button-label": "Tombol kirim TimePicker", - "supported-formats": "Format yang didukung: <1>sekarang- [digit]d/m/j/h/m", "to-label": "Ke" }, "zone": { diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index 28096ddddc2..2c22ca5c261 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -410,7 +410,6 @@ "action-buttons": { "delete": "", "edit-yaml": "", - "save": "", "save-exit": "" }, "title-delete-rule": "" @@ -3173,6 +3172,7 @@ "edit-pane": { "elements": { "dashboard": "Dashboard", + "local-variable": "", "multiple-elements": "", "multiple-elements-delete-text": "", "multiple-panels": "", @@ -4288,7 +4288,7 @@ "hide-time-picker": "Nascondi selettore tempo", "now-delay-description": "Escludi i dati recenti che potrebbero essere incompleti.", "now-delay-label": "Ritardo ora", - "refresh-live-dashboards-description": "Ridisegna continuamente i pannelli in cui l'intervallo di tempo fa riferimento a \"ora\"", + "refresh-live-dashboards-description": "", "refresh-live-dashboards-label": "Aggiorna dashboard in tempo reale", "time-options-label": "Opzioni ora", "time-zone-label": "Fuso orario", @@ -4738,9 +4738,6 @@ "logs-volumne-panel-list": { "body-no-logs-volume-available": "" }, - "make-datasource-setup": { - "aria-label-query": "" - }, "next": "", "next-prev-result": { "aria-label-next": "", @@ -4862,8 +4859,6 @@ "rich-history-utils": { "a-week-ago": "una settimana fa", "days-ago": "{{num}} giorni fa", - "default-from": "ora-1h", - "default-to": "ora", "today": "oggi", "two-weeks-ago": "due settimane fa", "yesterday": "ieri" @@ -8525,15 +8520,11 @@ "apply": "Applica intervallo di tempo", "aria-role": "Selezione dell'intervallo di tempo", "default-title": "Intervalli di tempo", - "example": "Esempio: per selezionare un intervallo di tempo da 10 minuti fa a ora", - "example-details": "Da: ora-10m A: ora", "example-title": "Esempi di intervalli di tempo", "from-label": "Da", "from-to": "Da {{timeOptionFrom}} a {{timeOptionTo}}", - "more-info": "Per ulteriori informazioni, consulta i <2>documenti<1>.", - "specify": "Specifica l'intervallo di tempo <1>", + "specify": "", "submit-button-label": "Pulsante di invio TimePicker", - "supported-formats": "Formati supportati: <1>ora-[numero]s/m/h/d/w", "to-label": "A" }, "zone": { diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index 1f41f360e3d..0df6cfcf16c 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -406,7 +406,6 @@ "action-buttons": { "delete": "", "edit-yaml": "", - "save": "", "save-exit": "" }, "title-delete-rule": "" @@ -3155,6 +3154,7 @@ "edit-pane": { "elements": { "dashboard": "ダッシュボード", + "local-variable": "", "multiple-elements": "", "multiple-elements-delete-text": "", "multiple-panels": "", @@ -4267,7 +4267,7 @@ "hide-time-picker": "時間ピッカーを非表示にする", "now-delay-description": "不完全な可能性のある最近のデータを除外します。", "now-delay-label": "現在の遅延", - "refresh-live-dashboards-description": "時間範囲が「現在」を参照するパネルを継続的に再描画する", + "refresh-live-dashboards-description": "", "refresh-live-dashboards-label": "ライブダッシュボードを更新", "time-options-label": "時間オプション", "time-zone-label": "タイムゾーン", @@ -4717,9 +4717,6 @@ "logs-volumne-panel-list": { "body-no-logs-volume-available": "" }, - "make-datasource-setup": { - "aria-label-query": "" - }, "next": "", "next-prev-result": { "aria-label-next": "", @@ -4841,8 +4838,6 @@ "rich-history-utils": { "a-week-ago": "1週間前", "days-ago": "{{num}}日前", - "default-from": "1時間前", - "default-to": "今", "today": "今日", "two-weeks-ago": "2週間前", "yesterday": "昨日" @@ -8494,15 +8489,11 @@ "apply": "時間範囲を適用", "aria-role": "時間範囲の選択", "default-title": "時間範囲", - "example": "例:10分前から現在までの時間範囲を選択するには", - "example-details": "開始:10分前 終了:現在", "example-title": "時間範囲の例", "from-label": "開始月", "from-to": "{{timeOptionFrom}}から{{timeOptionTo}}まで", - "more-info": "詳細については、<2>ドキュメント<1>をご覧ください。", - "specify": "時間範囲を指定 <1>", + "specify": "", "submit-button-label": "タイムピッカー送信ボタン", - "supported-formats": "サポートされている形式:<1>now-[digit]s/m/h/d/w", "to-label": "終了月" }, "zone": { diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index f7f39cf8892..e1b16792990 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -406,7 +406,6 @@ "action-buttons": { "delete": "", "edit-yaml": "", - "save": "", "save-exit": "" }, "title-delete-rule": "" @@ -3155,6 +3154,7 @@ "edit-pane": { "elements": { "dashboard": "대시보드", + "local-variable": "", "multiple-elements": "", "multiple-elements-delete-text": "", "multiple-panels": "", @@ -4267,7 +4267,7 @@ "hide-time-picker": "시간 선택기 숨기기", "now-delay-description": "불완전할 수 있는 최근 데이터를 제외합니다.", "now-delay-label": "현재 지연 시간", - "refresh-live-dashboards-description": "시간 범위가 '현재'를 참조하는 패널을 계속 업데이트합니다.", + "refresh-live-dashboards-description": "", "refresh-live-dashboards-label": "라이브 대시보드 새로 고침", "time-options-label": "시간 옵션", "time-zone-label": "시간대", @@ -4717,9 +4717,6 @@ "logs-volumne-panel-list": { "body-no-logs-volume-available": "" }, - "make-datasource-setup": { - "aria-label-query": "" - }, "next": "", "next-prev-result": { "aria-label-next": "", @@ -4841,8 +4838,6 @@ "rich-history-utils": { "a-week-ago": "일주일 전", "days-ago": "{{num}}일 전", - "default-from": "현재-1시간", - "default-to": "현재", "today": "오늘", "two-weeks-ago": "2주 전", "yesterday": "어제" @@ -8494,15 +8489,11 @@ "apply": "시간 범위 적용", "aria-role": "시간 범위 선택", "default-title": "시간 범위", - "example": "예: 10분 전부터 현재까지의 시간 범위를 선택하려면", - "example-details": "시작: 현재-10분, 종료: 현재", "example-title": "시간 범위 예시", "from-label": "시작 시간", "from-to": "{{timeOptionFrom}}~{{timeOptionTo}}", - "more-info": "자세한 내용은 <2>문서<1>를 확인하세요.", - "specify": "시간 범위 지정 <1>", + "specify": "", "submit-button-label": "시간 선택기 제출 버튼", - "supported-formats": "지원되는 형식: <1>현재-[digit]초/분/시간/일/주", "to-label": "종료 시간" }, "zone": { diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index 9d18a740242..25c1c09308c 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -410,7 +410,6 @@ "action-buttons": { "delete": "", "edit-yaml": "", - "save": "", "save-exit": "" }, "title-delete-rule": "" @@ -3173,6 +3172,7 @@ "edit-pane": { "elements": { "dashboard": "Dashboard", + "local-variable": "", "multiple-elements": "", "multiple-elements-delete-text": "", "multiple-panels": "", @@ -4288,7 +4288,7 @@ "hide-time-picker": "Tijdkiezer verbergen", "now-delay-description": "Sluit recente gegevens uit die mogelijk onvolledig zijn.", "now-delay-label": "Nu uitstellen", - "refresh-live-dashboards-description": "Teken voortdurend panelen opnieuw waar het tijdsbereik verwijst naar 'nu'", + "refresh-live-dashboards-description": "", "refresh-live-dashboards-label": "Live dashboards vernieuwen", "time-options-label": "Tijdopties", "time-zone-label": "Tijdzone", @@ -4738,9 +4738,6 @@ "logs-volumne-panel-list": { "body-no-logs-volume-available": "" }, - "make-datasource-setup": { - "aria-label-query": "" - }, "next": "", "next-prev-result": { "aria-label-next": "", @@ -4862,8 +4859,6 @@ "rich-history-utils": { "a-week-ago": "een week geleden", "days-ago": "{{num}} dagen geleden", - "default-from": "nu-1 uur", - "default-to": "nu", "today": "vandaag", "two-weeks-ago": "twee weken geleden", "yesterday": "gisteren" @@ -8525,15 +8520,11 @@ "apply": "Tijdsbereik toepassen", "aria-role": "Tijdsbereikselectie", "default-title": "Tijdsbereik", - "example": "Voorbeeld: om een tijdbereik van 10 minuten geleden tot nu te selecteren", - "example-details": "Van: nu-10 min Tot: nu", "example-title": "Voorbeeld tijdsbereiken", "from-label": "Van", "from-to": "{{timeOptionFrom}} tot {{timeOptionTo}}", - "more-info": "Zie <2>docs< 1 > voor meer informatie.", - "specify": "Geef tijdsbereik <1>", + "specify": "", "submit-button-label": "Knop TimePicker verzenden", - "supported-formats": "Ondersteunde formaten: <1>now-[digit]s/m/h/d/w", "to-label": "Aan" }, "zone": { diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index cd71050b236..6fdc8481bbe 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -418,7 +418,6 @@ "action-buttons": { "delete": "", "edit-yaml": "", - "save": "", "save-exit": "" }, "title-delete-rule": "" @@ -3209,6 +3208,7 @@ "edit-pane": { "elements": { "dashboard": "Pulpit", + "local-variable": "", "multiple-elements": "", "multiple-elements-delete-text": "", "multiple-panels": "", @@ -4330,7 +4330,7 @@ "hide-time-picker": "Ukryj selektor czasu", "now-delay-description": "Wyklucz najnowsze dane, które mogą być niekompletne.", "now-delay-label": "Teraz opóźnij", - "refresh-live-dashboards-description": "Generuj panele nieprzerwanie w przypadkach, dla których zakres czasu jest określony jako „teraz”", + "refresh-live-dashboards-description": "", "refresh-live-dashboards-label": "Odświeżaj pulpity w czasie rzeczywistym", "time-options-label": "Opcje czasu", "time-zone-label": "Strefa czasowa", @@ -4780,9 +4780,6 @@ "logs-volumne-panel-list": { "body-no-logs-volume-available": "" }, - "make-datasource-setup": { - "aria-label-query": "" - }, "next": "", "next-prev-result": { "aria-label-next": "", @@ -4904,8 +4901,6 @@ "rich-history-utils": { "a-week-ago": "tydzień temu", "days-ago": "{{num}} dni temu", - "default-from": "teraz – 1 godz.", - "default-to": "teraz", "today": "dzisiaj", "two-weeks-ago": "dwa tygodnie temu", "yesterday": "wczoraj" @@ -8587,15 +8582,11 @@ "apply": "Zastosuj zakres czasu", "aria-role": "Wybór zakresu czasu", "default-title": "Zakresy czasu", - "example": "Przykład: aby wybrać zakres czasu od 10 minut temu do teraz", - "example-details": "Od: now-10m Do: now", "example-title": "Przykładowe zakresy czasu", "from-label": "Od", "from-to": "{{timeOptionFrom}} do {{timeOptionTo}}", - "more-info": "Więcej informacji można znaleźć w <2>dokumentacji<1>.", - "specify": "Określ zakres czasu <1>", + "specify": "", "submit-button-label": "Przycisk przesyłania TimePicker", - "supported-formats": "Obsługiwane formaty: <1>now-[digit]s/m/h/d/w", "to-label": "Do" }, "zone": { diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index fa048892338..cab0c1b91cb 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -410,7 +410,6 @@ "action-buttons": { "delete": "", "edit-yaml": "", - "save": "", "save-exit": "" }, "title-delete-rule": "" @@ -3173,6 +3172,7 @@ "edit-pane": { "elements": { "dashboard": "", + "local-variable": "", "multiple-elements": "", "multiple-elements-delete-text": "", "multiple-panels": "", @@ -4288,7 +4288,7 @@ "hide-time-picker": "Ocultar seletor de hora", "now-delay-description": "Exclui dados recentes que podem estar incompletos.", "now-delay-label": "Atraso agora", - "refresh-live-dashboards-description": "Redesenha painéis continuamente onde as referências de intervalo de tempo são \"agora\"", + "refresh-live-dashboards-description": "", "refresh-live-dashboards-label": "Atualizar painéis de controle ao vivo", "time-options-label": "Opções de hora", "time-zone-label": "Fuso horário", @@ -4738,9 +4738,6 @@ "logs-volumne-panel-list": { "body-no-logs-volume-available": "" }, - "make-datasource-setup": { - "aria-label-query": "" - }, "next": "", "next-prev-result": { "aria-label-next": "", @@ -4862,8 +4859,6 @@ "rich-history-utils": { "a-week-ago": "há uma semana", "days-ago": "Há {{num}} dias", - "default-from": "agora-1h", - "default-to": "agora", "today": "hoje", "two-weeks-ago": "há duas semanas", "yesterday": "ontem" @@ -8525,15 +8520,11 @@ "apply": "", "aria-role": "Intervalo de tempo selecionado", "default-title": "Intervalos de tempo", - "example": "Exemplo: para selecionar um intervalo de tempo de 10 minutos atrás até agora", - "example-details": "De: agora-10m Até: agora", "example-title": "Exemplos de intervalos de tempo", "from-label": "", "from-to": "{{timeOptionFrom}} até {{timeOptionTo}}", - "more-info": "Para mais informações, consulte a <2>documentação <1>.", - "specify": "Especifique o intervalo de tempo <1>", + "specify": "", "submit-button-label": "Botão de envio do TimePicker", - "supported-formats": "Formatos compatíveis: <1>now-[digit]s/m/h/d/w", "to-label": "" }, "zone": { diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index 3b5e34996d8..af3cce19f54 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -410,7 +410,6 @@ "action-buttons": { "delete": "", "edit-yaml": "", - "save": "", "save-exit": "" }, "title-delete-rule": "" @@ -3173,6 +3172,7 @@ "edit-pane": { "elements": { "dashboard": "Painel de controlo", + "local-variable": "", "multiple-elements": "", "multiple-elements-delete-text": "", "multiple-panels": "", @@ -4288,7 +4288,7 @@ "hide-time-picker": "Ocultar seletor de hora", "now-delay-description": "Excluir dados recentes que possam estar incompletos.", "now-delay-label": "Atraso agora", - "refresh-live-dashboards-description": "Redesenhar continuamente os painéis onde o intervalo de tempo faz referência a 'agora'", + "refresh-live-dashboards-description": "", "refresh-live-dashboards-label": "Atualizar painéis de controlo ao vivo", "time-options-label": "Opções de tempo", "time-zone-label": "Fuso horário", @@ -4738,9 +4738,6 @@ "logs-volumne-panel-list": { "body-no-logs-volume-available": "" }, - "make-datasource-setup": { - "aria-label-query": "" - }, "next": "", "next-prev-result": { "aria-label-next": "", @@ -4862,8 +4859,6 @@ "rich-history-utils": { "a-week-ago": "há uma semana", "days-ago": "há {{num}} dias", - "default-from": "agora - 1 hora", - "default-to": "agora", "today": "hoje", "two-weeks-ago": "há duas semanas", "yesterday": "ontem" @@ -8525,15 +8520,11 @@ "apply": "Aplicar intervalo de tempo", "aria-role": "Seleção do intervalo de tempo", "default-title": "Intervalos de tempo", - "example": "Exemplo: para selecionar um intervalo de tempo de 10 minutos atrás até agora", - "example-details": "De: agora-10m Até: agora", "example-title": "Exemplo de intervalos de tempo", "from-label": "De", "from-to": "{{timeOptionFrom}} até {{timeOptionTo}}", - "more-info": "Para mais informações, consulte <2>docs <1>.", - "specify": "Especifique o intervalo de tempo <1>", + "specify": "", "submit-button-label": "Botão de envio do Seletor de hora", - "supported-formats": "Formatos suportados: <1>agora-[dígito]s/m/h/d/s", "to-label": "Para" }, "zone": { diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index 77799cb4ec9..8c1dd0a7450 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -418,7 +418,6 @@ "action-buttons": { "delete": "", "edit-yaml": "", - "save": "", "save-exit": "" }, "title-delete-rule": "" @@ -3209,6 +3208,7 @@ "edit-pane": { "elements": { "dashboard": "Дашборд", + "local-variable": "", "multiple-elements": "", "multiple-elements-delete-text": "", "multiple-panels": "", @@ -4330,7 +4330,7 @@ "hide-time-picker": "Скрыть указатель времени", "now-delay-description": "Исключите последние данные, которые могут быть неполными.", "now-delay-label": "Задержка в текущий момент", - "refresh-live-dashboards-description": "Постоянно обновляйте панели, где временной диапазон ссылается на «текущий момент».", + "refresh-live-dashboards-description": "", "refresh-live-dashboards-label": "Обновить дашборды в реальном времени", "time-options-label": "Параметры времени", "time-zone-label": "Часовой пояс", @@ -4780,9 +4780,6 @@ "logs-volumne-panel-list": { "body-no-logs-volume-available": "" }, - "make-datasource-setup": { - "aria-label-query": "" - }, "next": "", "next-prev-result": { "aria-label-next": "", @@ -4904,8 +4901,6 @@ "rich-history-utils": { "a-week-ago": "неделю назад", "days-ago": "{{num}} дн. назад", - "default-from": "текущий момент-1 ч", - "default-to": "текущий момент", "today": "сегодня", "two-weeks-ago": "две недели назад", "yesterday": "вчера" @@ -8587,15 +8582,11 @@ "apply": "Применить временной диапазон", "aria-role": "Выбор временного диапазона", "default-title": "Временные диапазоны", - "example": "Пример: выберите временной диапазон от 10 минут назад до текущего момента", - "example-details": "Время начала: текущий момент-10 м Время окончания: текущий момент", "example-title": "Примеры временных диапазонов", "from-label": "Время начала", "from-to": "{{timeOptionFrom}} до {{timeOptionTo}}", - "more-info": "Для получения дополнительной информации см. <2>документацию<1>.", - "specify": "Укажите временной диапазон <1>", + "specify": "", "submit-button-label": "Кнопка отправки указателя времени", - "supported-formats": "Поддерживаемые форматы: <1>текущий момент-[цифра] с/м/ч/д/н", "to-label": "Время окончания" }, "zone": { diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index 0dd371119bd..190031535bd 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -410,7 +410,6 @@ "action-buttons": { "delete": "", "edit-yaml": "", - "save": "", "save-exit": "" }, "title-delete-rule": "" @@ -3173,6 +3172,7 @@ "edit-pane": { "elements": { "dashboard": "Instrumentpanel", + "local-variable": "", "multiple-elements": "", "multiple-elements-delete-text": "", "multiple-panels": "", @@ -4288,7 +4288,7 @@ "hide-time-picker": "Dölj tidsväljare", "now-delay-description": "Uteslut senaste data som kan vara ofullständiga.", "now-delay-label": "Fördröj nu", - "refresh-live-dashboards-description": "Rita om paneler kontinuerligt där tidsintervallet refererar till ”nu”", + "refresh-live-dashboards-description": "", "refresh-live-dashboards-label": "Uppdatera paneler i realtid", "time-options-label": "Tidsalternativ", "time-zone-label": "Tidszon", @@ -4738,9 +4738,6 @@ "logs-volumne-panel-list": { "body-no-logs-volume-available": "" }, - "make-datasource-setup": { - "aria-label-query": "" - }, "next": "", "next-prev-result": { "aria-label-next": "", @@ -4862,8 +4859,6 @@ "rich-history-utils": { "a-week-ago": "en vecka sedan", "days-ago": "{{num}} dagar sedan", - "default-from": "nu–1 timme", - "default-to": "nu", "today": "idag", "two-weeks-ago": "två veckor sedan", "yesterday": "igår" @@ -8525,15 +8520,11 @@ "apply": "Tillämpa tidsintervall", "aria-role": "Val av tidsintervall", "default-title": "Tidsintervall", - "example": "Exempel: för att välja ett tidsintervall från 10 minuter sedan fram till nu", - "example-details": "Från: now-10m Till: now", "example-title": "Exempel på tidsintervall", "from-label": "Från", "from-to": "{{timeOptionFrom}} till {{timeOptionTo}}", - "more-info": "För mer information se <2>dokumentationen<1>.", - "specify": "Ange tidsintervall <1>", + "specify": "", "submit-button-label": "Knapp för att skicka TimePicker", - "supported-formats": "Format som stöds: <1> now-[digit]s/m/h/d/w", "to-label": "Till" }, "zone": { diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index 6f0c0d625ee..769660b1aa5 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -410,7 +410,6 @@ "action-buttons": { "delete": "", "edit-yaml": "", - "save": "", "save-exit": "" }, "title-delete-rule": "" @@ -3173,6 +3172,7 @@ "edit-pane": { "elements": { "dashboard": "Pano", + "local-variable": "", "multiple-elements": "", "multiple-elements-delete-text": "", "multiple-panels": "", @@ -4288,7 +4288,7 @@ "hide-time-picker": "Zaman seçiciyi gizle", "now-delay-description": "Tamamlanmamış olabilecek en son verileri hariç tutun.", "now-delay-label": "Şimdi ertele", - "refresh-live-dashboards-description": "Zaman aralığı \"şimdi\"yi referans alan panelleri sürekli olarak yeniden çizin.", + "refresh-live-dashboards-description": "", "refresh-live-dashboards-label": "Canlı panoları yenile", "time-options-label": "Zaman seçenekleri", "time-zone-label": "Zaman dilimi", @@ -4738,9 +4738,6 @@ "logs-volumne-panel-list": { "body-no-logs-volume-available": "" }, - "make-datasource-setup": { - "aria-label-query": "" - }, "next": "", "next-prev-result": { "aria-label-next": "", @@ -4862,8 +4859,6 @@ "rich-history-utils": { "a-week-ago": "bir hafta önce", "days-ago": "{{num}} gün önce", - "default-from": "şimdi - 1 saat", - "default-to": "şimdi", "today": "bugün", "two-weeks-ago": "iki hafta önce", "yesterday": "dün" @@ -8525,15 +8520,11 @@ "apply": "Zaman aralığını uygula", "aria-role": "Zaman aralığı seçimi", "default-title": "Zaman aralıkları", - "example": "Örnek: Şu an ile 10 dakika önce arasındaki zaman aralığını seçmek için", - "example-details": "Başlangıç: now-10m Bitiş: now", "example-title": "Örnek zaman aralıkları", "from-label": "Başlangıç", "from-to": "{{timeOptionFrom}} - {{timeOptionTo}}", - "more-info": "Daha fazla bilgi için <2>belgelere<1> bakın.", - "specify": "Zaman aralığını belirtin <1>", + "specify": "", "submit-button-label": "TimePicker gönder düğmesi", - "supported-formats": "Desteklenen biçimler: <1>now-[rakam]s/m/h/d/w", "to-label": "Bitiş" }, "zone": { diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 00a8ae11080..23dd1d2468c 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -406,7 +406,6 @@ "action-buttons": { "delete": "", "edit-yaml": "", - "save": "", "save-exit": "" }, "title-delete-rule": "" @@ -3155,6 +3154,7 @@ "edit-pane": { "elements": { "dashboard": "", + "local-variable": "", "multiple-elements": "", "multiple-elements-delete-text": "", "multiple-panels": "", @@ -4267,7 +4267,7 @@ "hide-time-picker": "隐藏时间选择器", "now-delay-description": "排除可能不完整的最近数据。", "now-delay-label": "现在延迟", - "refresh-live-dashboards-description": "连续重新绘制在其中时间范围引用“现在”的面板。", + "refresh-live-dashboards-description": "", "refresh-live-dashboards-label": "刷新直播仪表板", "time-options-label": "时间选项", "time-zone-label": "时区", @@ -4717,9 +4717,6 @@ "logs-volumne-panel-list": { "body-no-logs-volume-available": "" }, - "make-datasource-setup": { - "aria-label-query": "" - }, "next": "", "next-prev-result": { "aria-label-next": "", @@ -4841,8 +4838,6 @@ "rich-history-utils": { "a-week-ago": "一周前", "days-ago": "{{num}} 天前", - "default-from": "现在-1 小时", - "default-to": "现在", "today": "今天", "two-weeks-ago": "两周前", "yesterday": "昨天" @@ -8494,15 +8489,11 @@ "apply": "", "aria-role": "时间范围选择", "default-title": "时间范围", - "example": "例如:选择从 10 分钟前到现在的时间范围", - "example-details": "从:now-10m 到:now", "example-title": "示例时间范围", "from-label": "", "from-to": "{{timeOptionFrom}} 到 {{timeOptionTo}}", - "more-info": "如需了解更多信息,请参阅<2>文档<1>。", - "specify": "指定时间范围 <1>", + "specify": "", "submit-button-label": "TimePicker 提交按钮", - "supported-formats": "支持的格式:<1>now-[digit]s/m/h/d/w", "to-label": "" }, "zone": { diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index f9d0cdf2425..b73944a5257 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -406,7 +406,6 @@ "action-buttons": { "delete": "", "edit-yaml": "", - "save": "", "save-exit": "" }, "title-delete-rule": "" @@ -3155,6 +3154,7 @@ "edit-pane": { "elements": { "dashboard": "儀表板", + "local-variable": "", "multiple-elements": "", "multiple-elements-delete-text": "", "multiple-panels": "", @@ -4267,7 +4267,7 @@ "hide-time-picker": "隱藏時間選擇器", "now-delay-description": "排除最近可能不完整的資料。", "now-delay-label": "現在延遲", - "refresh-live-dashboards-description": "持續重新繪製時間範圍引用「現在」的面板", + "refresh-live-dashboards-description": "", "refresh-live-dashboards-label": "重新整理即時儀表板", "time-options-label": "時間選項", "time-zone-label": "時區", @@ -4717,9 +4717,6 @@ "logs-volumne-panel-list": { "body-no-logs-volume-available": "" }, - "make-datasource-setup": { - "aria-label-query": "" - }, "next": "", "next-prev-result": { "aria-label-next": "", @@ -4841,8 +4838,6 @@ "rich-history-utils": { "a-week-ago": "一個星期前", "days-ago": "{{num}} 天前", - "default-from": "現在 -1 小時", - "default-to": "現在", "today": "今天", "two-weeks-ago": "兩週前", "yesterday": "昨天" @@ -8494,15 +8489,11 @@ "apply": "套用時間範圍", "aria-role": "時間範圍選取", "default-title": "時間範圍", - "example": "例如:選擇 10 分鐘前到現在的時間範圍", - "example-details": "自:現在 -10 分鐘 至:現在", "example-title": "時間範圍範例", "from-label": "自", "from-to": "{{timeOptionFrom}} 至 {{timeOptionTo}}", - "more-info": "如需更多資訊,請參閱<2>文件<1>。", - "specify": "指定時間範圍<1>", + "specify": "", "submit-button-label": "TimePicker 提交按鈕", - "supported-formats": "支援的格式:<1>now-[digit]s/m/h/d/w", "to-label": "至" }, "zone": { From 067d7b4146c543110d282ccab6c617c8a25d85f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Farkas?= Date: Thu, 24 Apr 2025 10:42:11 +0200 Subject: [PATCH 078/146] feature-toggles: change ownership (#104440) --- pkg/services/featuremgmt/registry.go | 6 ++--- pkg/services/featuremgmt/toggles_gen.csv | 6 ++--- pkg/services/featuremgmt/toggles_gen.json | 27 +++++++++++++++-------- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 5b45c8a17dd..087b7327b81 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -560,21 +560,21 @@ var ( Name: "queryService", Description: "Register /apis/query.grafana.app/ -- will eventually replace /api/ds/query", Stage: FeatureStageExperimental, - Owner: grafanaAppPlatformSquad, + Owner: grafanaDatasourcesCoreServicesSquad, RequiresRestart: true, // Adds a route at startup }, { Name: "queryServiceRewrite", Description: "Rewrite requests targeting /ds/query to the query service", Stage: FeatureStageExperimental, - Owner: grafanaAppPlatformSquad, + Owner: grafanaDatasourcesCoreServicesSquad, RequiresRestart: true, // changes the API routing }, { Name: "queryServiceFromUI", Description: "Routes requests to the new query service", Stage: FeatureStageExperimental, - Owner: grafanaAppPlatformSquad, + Owner: grafanaDatasourcesCoreServicesSquad, FrontendOnly: true, // and can change at startup }, { diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 879a3810a37..fe9151fc18c 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -72,9 +72,9 @@ dashboardDisableSchemaValidationV1,experimental,@grafana/grafana-app-platform-sq dashboardDisableSchemaValidationV2,experimental,@grafana/grafana-app-platform-squad,false,false,false dashboardSchemaValidationLogging,experimental,@grafana/grafana-app-platform-squad,false,false,false datasourceQueryTypes,experimental,@grafana/grafana-app-platform-squad,false,true,false -queryService,experimental,@grafana/grafana-app-platform-squad,false,true,false -queryServiceRewrite,experimental,@grafana/grafana-app-platform-squad,false,true,false -queryServiceFromUI,experimental,@grafana/grafana-app-platform-squad,false,false,true +queryService,experimental,@grafana/grafana-datasources-core-services,false,true,false +queryServiceRewrite,experimental,@grafana/grafana-datasources-core-services,false,true,false +queryServiceFromUI,experimental,@grafana/grafana-datasources-core-services,false,false,true queryServiceFromExplore,experimental,@grafana/grafana-datasources-core-services,false,false,true cloudWatchBatchQueries,preview,@grafana/aws-datasources,false,false,false recoveryThreshold,GA,@grafana/alerting-squad,false,true,false diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 7d130cc9df4..4a11a5109c7 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2462,13 +2462,16 @@ { "metadata": { "name": "queryService", - "resourceVersion": "1745339544057", - "creationTimestamp": "2024-04-19T09:26:21Z" + "resourceVersion": "1745480536808", + "creationTimestamp": "2024-04-19T09:26:21Z", + "annotations": { + "grafana.app/updatedTimestamp": "2025-04-24 07:42:16.80869 +0000 UTC" + } }, "spec": { "description": "Register /apis/query.grafana.app/ -- will eventually replace /api/ds/query", "stage": "experimental", - "codeowner": "@grafana/grafana-app-platform-squad", + "codeowner": "@grafana/grafana-datasources-core-services", "requiresRestart": true } }, @@ -2488,26 +2491,32 @@ { "metadata": { "name": "queryServiceFromUI", - "resourceVersion": "1745339544057", - "creationTimestamp": "2024-04-19T09:26:21Z" + "resourceVersion": "1745480536808", + "creationTimestamp": "2024-04-19T09:26:21Z", + "annotations": { + "grafana.app/updatedTimestamp": "2025-04-24 07:42:16.80869 +0000 UTC" + } }, "spec": { "description": "Routes requests to the new query service", "stage": "experimental", - "codeowner": "@grafana/grafana-app-platform-squad", + "codeowner": "@grafana/grafana-datasources-core-services", "frontend": true } }, { "metadata": { "name": "queryServiceRewrite", - "resourceVersion": "1745339544057", - "creationTimestamp": "2024-04-19T09:26:21Z" + "resourceVersion": "1745480536808", + "creationTimestamp": "2024-04-19T09:26:21Z", + "annotations": { + "grafana.app/updatedTimestamp": "2025-04-24 07:42:16.80869 +0000 UTC" + } }, "spec": { "description": "Rewrite requests targeting /ds/query to the query service", "stage": "experimental", - "codeowner": "@grafana/grafana-app-platform-squad", + "codeowner": "@grafana/grafana-datasources-core-services", "requiresRestart": true } }, From 356c9793282de46b8346dbcf84aa6fdfe2096e71 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Thu, 24 Apr 2025 09:50:29 +0100 Subject: [PATCH 079/146] Chore: Enable `no-constant-condition` eslint rule (#104298) --- eslint.config.js | 1 + .../api/browseDashboardsAPI.ts | 22 +-------- .../grafana-testdata-datasource/runStreams.ts | 10 ++-- .../geomap/layers/data/dayNightLayer.tsx | 46 +------------------ .../geomap/layers/data/geojsonDynamic.ts | 12 ++--- .../panel/geomap/layers/data/geojsonLayer.ts | 11 ++--- public/locales/en-US/grafana.json | 3 -- 7 files changed, 19 insertions(+), 86 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 2fd159f0fcf..ece56c24c9d 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -132,6 +132,7 @@ module.exports = [ 'no-redeclare': 'off', '@typescript-eslint/no-redeclare': ['error'], 'unicorn/no-empty-file': 'error', + 'no-constant-condition': 'error', }, }, { diff --git a/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts b/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts index 89749f285d5..addc640cef9 100644 --- a/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts +++ b/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts @@ -289,29 +289,11 @@ export const browseDashboardsAPI = createApi({ // Delete all the dashboards sequentially // TODO error handling here for (const dashboardUID of selectedDashboards) { - const response = await getDashboardAPI().deleteDashboard(dashboardUID, true); + await getDashboardAPI().deleteDashboard(dashboardUID, true); // handling success alerts for these feature toggles // for legacy response, the success alert will be triggered by showSuccessAlert function in public/app/core/services/backend_srv.ts - if (false) { - // TODO: change this to a feature flag when dashboard restore is reworked - const name = response?.title; - - if (name) { - const payload = config.featureToggles.kubernetesDashboards - ? ['Dashboard moved to Recently deleted'] - : [ - t('browse-dashboards.soft-delete.success', 'Dashboard {{name}} moved to Recently deleted', { - name, - }), - ]; - - appEvents.publish({ - type: AppEvents.alertSuccess.name, - payload, - }); - } - } else if (config.featureToggles.kubernetesDashboards) { + if (config.featureToggles.kubernetesDashboards) { appEvents.publish({ type: AppEvents.alertSuccess.name, payload: ['Dashboard deleted'], diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/runStreams.ts b/public/app/plugins/datasource/grafana-testdata-datasource/runStreams.ts index 26a6af2449b..3d769e7b7f9 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/runStreams.ts +++ b/public/app/plugins/datasource/grafana-testdata-datasource/runStreams.ts @@ -104,12 +104,10 @@ export function runSignalStream( }; // Fill the buffer on init - if (true) { - let time = Date.now() - maxDataPoints * speed; - for (let i = 0; i < maxDataPoints; i++) { - addNextRow(time); - time += speed; - } + let time = Date.now() - maxDataPoints * speed; + for (let i = 0; i < maxDataPoints; i++) { + addNextRow(time); + time += speed; } const pushNextEvent = () => { diff --git a/public/app/plugins/panel/geomap/layers/data/dayNightLayer.tsx b/public/app/plugins/panel/geomap/layers/data/dayNightLayer.tsx index dff87ea84bc..346c922900c 100644 --- a/public/app/plugins/panel/geomap/layers/data/dayNightLayer.tsx +++ b/public/app/plugins/panel/geomap/layers/data/dayNightLayer.tsx @@ -1,7 +1,5 @@ import Feature from 'ol/Feature'; import Map from 'ol/Map'; -import { Coordinate } from 'ol/coordinate'; -import { MultiLineString } from 'ol/geom'; import Point from 'ol/geom/Point'; import { Group as LayerGroup } from 'ol/layer'; import VectorImage from 'ol/layer/VectorImage'; @@ -16,9 +14,7 @@ import { MapLayerOptions, PanelData, GrafanaTheme2, - EventBus, - DataHoverEvent, - DataHoverClearEvent, + EventBus } from '@grafana/data'; export enum ShowTime { @@ -74,8 +70,6 @@ export const dayNightLayer: MapLayerRegistryItem = { // DayNight source const source = new DayNight({}); const sourceMethods = Object.getPrototypeOf(source); - const sourceLine = new DayNight({}); - const sourceLineMethods = Object.getPrototypeOf(sourceLine); // Night polygon const vectorLayer = new VectorImage({ @@ -160,44 +154,6 @@ export const dayNightLayer: MapLayerRegistryItem = { // Crosshair sharing subscriptions const subscriptions = new Subscription(); - if (false) { - subscriptions.add( - eventBus.subscribe(DataHoverEvent, (event) => { - const time = event.payload?.point?.time; - if (time) { - const lineTime = new Date(time); - const nightLinePoints = sourceLine.getCoordinates(lineTime.toString(), 'line'); - nightLineLayer.getSource()?.clear(); - const lineStringArray: Coordinate[][] = []; - for (let l = 0; l < nightLinePoints.length - 1; l++) { - const x1: number = Object.values(nightLinePoints[l])[0]; - const y1: number = Object.values(nightLinePoints[l])[1]; - const x2: number = Object.values(nightLinePoints[l + 1])[0]; - const y2: number = Object.values(nightLinePoints[l + 1])[1]; - const lineString = [fromLonLat([x1, y1]), fromLonLat([x2, y2])]; - lineStringArray.push(lineString); - } - nightLineLayer.getSource()?.addFeature( - new Feature({ - geometry: new MultiLineString(lineStringArray), - }) - ); - - let sunLinePos: number[] = []; - sunLinePos = sourceLineMethods.getSunPosition(lineTime); - sunLineFeature.getGeometry()?.setCoordinates(fromLonLat(sunLinePos)); - sunLineFeature.setStyle([sunLineStyle, sunLineStyleDash]); - } - }) - ); - - subscriptions.add( - eventBus.subscribe(DataHoverClearEvent, (event) => { - nightLineLayer.getSource()?.clear(); - sunLineFeature.setStyle(new Style({})); - }) - ); - } return { init: () => layer, diff --git a/public/app/plugins/panel/geomap/layers/data/geojsonDynamic.ts b/public/app/plugins/panel/geomap/layers/data/geojsonDynamic.ts index 924a732707d..19ee2480669 100644 --- a/public/app/plugins/panel/geomap/layers/data/geojsonDynamic.ts +++ b/public/app/plugins/panel/geomap/layers/data/geojsonDynamic.ts @@ -108,12 +108,12 @@ export const dynamicGeoJSONLayer: MapLayerRegistryItem(); diff --git a/public/app/plugins/panel/geomap/layers/data/geojsonLayer.ts b/public/app/plugins/panel/geomap/layers/data/geojsonLayer.ts index de1bc24eb4e..176126f4183 100644 --- a/public/app/plugins/panel/geomap/layers/data/geojsonLayer.ts +++ b/public/app/plugins/panel/geomap/layers/data/geojsonLayer.ts @@ -102,12 +102,11 @@ export const geojsonLayer: MapLayerRegistryItem = { } } } - if (true) { - const s = await getStyleConfigState(config.style); - styles.push({ - state: s, - }); - } + + const s = await getStyleConfigState(config.style); + styles.push({ + state: s, + }); const polyStyleStrings: string[] = Object.values(GeoJSONPolyStyles); const pointStyleStrings: string[] = Object.values(GeoJSONPointStyles); diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index a975f9585ed..5f5704eb8a8 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -2680,9 +2680,6 @@ "clear": "Clear search and filters", "text": "No results found for your query" }, - "soft-delete": { - "success": "Dashboard {{name}} moved to Recently deleted" - }, "text-this-repository-is-read-only": "If you have direct access to the target, copy the JSON and paste it there." }, "canvas": { From 1584349b990237038eeb58cff3b49346a57e47d2 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 24 Apr 2025 10:57:24 +0200 Subject: [PATCH 080/146] Zanzana: Use authz client (#104037) * Zanzana: use client from authzlib * update go.sum * use user UID for debugging * Remove unused function --- pkg/services/authz/zanzana/client/client.go | 76 +++---------------- .../authz/zanzana/client/shadow_client.go | 4 +- 2 files changed, 14 insertions(+), 66 deletions(-) diff --git a/pkg/services/authz/zanzana/client/client.go b/pkg/services/authz/zanzana/client/client.go index 85c92a170e6..964b00d7724 100644 --- a/pkg/services/authz/zanzana/client/client.go +++ b/pkg/services/authz/zanzana/client/client.go @@ -3,12 +3,12 @@ package client import ( "context" + authzlib "github.com/grafana/authlib/authz" authzv1 "github.com/grafana/authlib/authz/proto/v1" authlib "github.com/grafana/authlib/types" "go.opentelemetry.io/otel" "google.golang.org/grpc" - "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/infra/log" authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1" ) @@ -18,16 +18,19 @@ var _ authlib.AccessClient = (*Client)(nil) var tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/authz/zanzana/client") type Client struct { - logger log.Logger - authz authzv1.AuthzServiceClient - authzext authzextv1.AuthzExtentionServiceClient + logger log.Logger + authz authzv1.AuthzServiceClient + authzext authzextv1.AuthzExtentionServiceClient + authzlibclient *authzlib.ClientImpl } func New(cc grpc.ClientConnInterface) (*Client, error) { + authzlibclient := authzlib.NewClient(cc, authzlib.WithTracerClientOption(tracer)) c := &Client{ - authz: authzv1.NewAuthzServiceClient(cc), - authzext: authzextv1.NewAuthzExtentionServiceClient(cc), - logger: log.New("zanzana-client"), + authzlibclient: authzlibclient, + authz: authzv1.NewAuthzServiceClient(cc), + authzext: authzextv1.NewAuthzExtentionServiceClient(cc), + logger: log.New("zanzana-client"), } return c, nil @@ -37,69 +40,14 @@ func (c *Client) Check(ctx context.Context, id authlib.AuthInfo, req authlib.Che ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Check") defer span.End() - res, err := c.authz.Check(ctx, &authzv1.CheckRequest{ - Subject: id.GetUID(), - Verb: req.Verb, - Group: req.Group, - Resource: req.Resource, - Namespace: req.Namespace, - Name: req.Name, - Subresource: req.Subresource, - Path: req.Path, - Folder: req.Folder, - }) - - if err != nil { - return authlib.CheckResponse{}, err - } - - return authlib.CheckResponse{Allowed: res.GetAllowed()}, nil + return c.authzlibclient.Check(ctx, id, req) } func (c *Client) Compile(ctx context.Context, id authlib.AuthInfo, req authlib.ListRequest) (authlib.ItemChecker, error) { ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Compile") defer span.End() - res, err := c.authz.List(ctx, &authzv1.ListRequest{ - Subject: id.GetUID(), - Group: req.Group, - Verb: utils.VerbList, - Resource: req.Resource, - Namespace: req.Namespace, - }) - - if err != nil { - return nil, err - } - - return newItemChecker(res), nil -} - -func newItemChecker(res *authzv1.ListResponse) authlib.ItemChecker { - // if we can see all resource of this type we can just return a function that always return true - if res.GetAll() { - return func(_, _ string) bool { return true } - } - - folders := make(map[string]struct{}, len(res.Folders)) - for _, f := range res.Folders { - folders[f] = struct{}{} - } - - items := make(map[string]struct{}, len(res.Items)) - for _, i := range res.Items { - items[i] = struct{}{} - } - - return func(name, folder string) bool { - if _, ok := items[name]; ok { - return true - } - if _, ok := folders[folder]; ok { - return true - } - return false - } + return c.authzlibclient.Compile(ctx, id, req) } func (c *Client) Read(ctx context.Context, req *authzextv1.ReadRequest) (*authzextv1.ReadResponse, error) { diff --git a/pkg/services/authz/zanzana/client/shadow_client.go b/pkg/services/authz/zanzana/client/shadow_client.go index 9e71332ef88..16dacfd5029 100644 --- a/pkg/services/authz/zanzana/client/shadow_client.go +++ b/pkg/services/authz/zanzana/client/shadow_client.go @@ -51,7 +51,7 @@ func (c *ShadowClient) Check(ctx context.Context, id authlib.AuthInfo, req authl if acErr == nil { if res.Allowed != acRes.Allowed { c.metrics.evaluationStatusTotal.WithLabelValues("error").Inc() - c.logger.Warn("Zanzana check result does not match", "expected", acRes.Allowed, "actual", res.Allowed) + c.logger.Warn("Zanzana check result does not match", "expected", acRes.Allowed, "actual", res.Allowed, "user", id.GetUID(), "request", req) } else { c.metrics.evaluationStatusTotal.WithLabelValues("success").Inc() } @@ -99,7 +99,7 @@ func (c *ShadowClient) Compile(ctx context.Context, id authlib.AuthInfo, req aut zanzanaRes := zanzanaItemChecker(name, folder) if zanzanaRes != rbacRes { c.metrics.evaluationStatusTotal.WithLabelValues("error").Inc() - c.logger.Warn("Zanzana compile result does not match", "expected", rbacRes, "actual", zanzanaRes) + c.logger.Warn("Zanzana compile result does not match", "expected", rbacRes, "actual", zanzanaRes, "name", name, "folder", folder) } else { c.metrics.evaluationStatusTotal.WithLabelValues("success").Inc() } From 10df5d6f23f4c07f4b7a3687183f63b93349681a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Fern=C3=A1ndez?= Date: Thu, 24 Apr 2025 11:08:57 +0200 Subject: [PATCH 081/146] i18n: unify locale codes to match language structure (#104415) --- .../app/core/internationalization/locales.ts | 375 ++++++++++++------ 1 file changed, 256 insertions(+), 119 deletions(-) diff --git a/public/app/core/internationalization/locales.ts b/public/app/core/internationalization/locales.ts index 4e7ce32ed8a..9b811d39ba6 100644 --- a/public/app/core/internationalization/locales.ts +++ b/public/app/core/internationalization/locales.ts @@ -1,5 +1,4 @@ -// List hard-coded locales from https://github.com/moment/moment/tree/develop/locale - +// Info: https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry interface Locale { name: string; code: string; @@ -7,140 +6,278 @@ interface Locale { // TODO re-check translations export const LOCALES: Locale[] = [ + // Afrikaans - Standard { name: 'Afrikaans', code: 'af' }, + // Arabic - Standard { name: 'العربية', code: 'ar' }, - { name: 'العربية (الجزائر)', code: 'ar-dz' }, - { name: 'العربية (الكويت)', code: 'ar-kw' }, - { name: 'العربية (ليبيا)', code: 'ar-ly' }, - { name: 'العربية (المغرب)', code: 'ar-ma' }, - { name: 'العربية (فلسطين)', code: 'ar-ps' }, - { name: 'العربية (السعودية)', code: 'ar-sa' }, - { name: 'العربية (تونس)', code: 'ar-tn' }, - { name: 'Azərbaycanca', code: 'az' }, - { name: 'Беларуская', code: 'be' }, - { name: 'български език', code: 'bg' }, + // Arabic - Algeria + { name: 'العربية (الجزائر)', code: 'ar-DZ' }, + // Arabic - Kuwait + { name: 'العربية (الكويت)', code: 'ar-KW' }, + // Arabic - Libya + { name: 'العربية (ليبيا)', code: 'ar-LY' }, + // Arabic - Morocco + { name: 'العربية (المغرب)', code: 'ar-MA' }, + // Arabic - Palestine + { name: 'العربية (فلسطين)', code: 'ar-PS' }, + // Arabic - Saudi Arabia + { name: 'العربية (السعودية)', code: 'ar-SA' }, + // Arabic - Tunisia + { name: 'العربية (تونس)', code: 'ar-TN' }, + // Azerbaijani - Azerbaijan + { name: 'Azərbaycan dili', code: 'az' }, + // Belarusian - Belarus + { name: 'Беларуская мова', code: 'be-BY' }, + // Bulgarian - Bulgaria + { name: 'Български език', code: 'bg-BG' }, + // Bambara - Mali { name: 'Bamanankan', code: 'bm' }, - { name: 'Bengali', code: 'bn' }, // TODO translate : বাংলা ??? - { name: 'Bengali (Bangladesh)', code: 'bn-bd' }, // TODO translate - { name: 'Tibetan', code: 'bo' }, // TODO translate + // Bengali - Standard + { name: 'বাংলা', code: 'bn' }, + // Bengali - Bangladesh + { name: 'বাংলা', code: 'bn-BD' }, + // Tibetan - Tibet (China) and Bhutan + { name: 'བོད་ཡིག', code: 'bo' }, + // Breton - Brittany (France) { name: 'Brezhoneg', code: 'br' }, - { name: 'Босански', code: 'bs' }, - { name: 'Catalán', code: 'ca' }, - { name: 'Čeština', code: 'cs' }, - { name: 'Cymraeg', code: 'cy' }, - { name: 'Чӑвашла', code: 'cv' }, - { name: 'Dansk', code: 'da' }, - { name: 'Deutsch', code: 'de' }, - { name: 'Deutsch (Österreich)', code: 'de-at' }, - { name: 'Deutsch (Schweiz)', code: 'de-ch' }, - { name: 'ދިވެހި', code: 'dv' }, - { name: 'Ελληνικά', code: 'el' }, - { name: 'English (Australia)', code: 'en-au' }, - { name: 'English (Canada)', code: 'en-ca' }, - { name: 'English (United Kingdom)', code: 'en-gb' }, - { name: 'English (Ireland)', code: 'en-ie' }, - { name: 'English (Israel)', code: 'en-il' }, - { name: 'English (India)', code: 'en-in' }, - { name: 'English (New Zealand)', code: 'en-nz' }, - { name: 'English (Singapore)', code: 'en-sg' }, - { name: 'English (United States)', code: 'en' }, + // Bosnian - Bosnia and Herzegovina + { name: 'Bosanski jezik', code: 'bs' }, + // Catalan - Catalonia (Spain) + { name: 'Català', code: 'ca-ES' }, + // Czech - Czech Republic + { name: 'Čeština', code: 'cs-CZ' }, + // Welsh - Wales (United Kingdom) + { name: 'Cymraeg', code: 'cy-GB' }, + // Chuvash - Chuvashia (Russia) + { name: 'Чӑвашла', code: 'cv-RU' }, + // Danish - Denmark + { name: 'Dansk', code: 'da-DK' }, + // German - Germany + { name: 'Deutsch', code: 'de-DE' }, + // German - Austria + { name: 'Deutsch (Österreich)', code: 'de-AT' }, + // German - Switzerland + { name: 'Deutsch (Schweiz)', code: 'de-CH' }, + // Divehi/Maldivian - Maldives + { name: 'ދިވެހި', code: 'dv-MV' }, + // Greek - Greece + { name: 'Ελληνικά', code: 'el-GR' }, + // English - Australia + { name: 'English (Australia)', code: 'en-AU' }, + // English - Canada + { name: 'English (Canada)', code: 'en-CA' }, + // English - United Kingdom + { name: 'English (United Kingdom)', code: 'en-GB' }, + // English - Ireland + { name: 'English (Ireland)', code: 'en-IE' }, + // English - Israel + { name: 'English (Israel)', code: 'en-IL' }, + // English - India + { name: 'English (India)', code: 'en-IN' }, + // English - New Zealand + { name: 'English (New Zealand)', code: 'en-NZ' }, + // English - Singapore + { name: 'English (Singapore)', code: 'en-SG' }, + // English - United States + { name: 'English (United States)', code: 'en-US' }, + // Esperanto - International Auxiliary Language { name: 'Esperanto', code: 'eo' }, - { name: 'Español', code: 'es' }, - { name: 'Español (República Dominicana)', code: 'es-do' }, - { name: 'Español (México)', code: 'es-mx' }, - { name: 'Español (Estados Unidos)', code: 'es-us' }, - { name: 'Eesti keel', code: 'et' }, - { name: 'Euskara', code: 'eu' }, - { name: 'فارسی', code: 'fa' }, - { name: 'Filipino', code: 'fil' }, - { name: 'Suomi', code: 'fi' }, - { name: 'Føroyskt', code: 'fo' }, - { name: 'Français', code: 'fr' }, - { name: 'Français (Canada)', code: 'fr-ca' }, - { name: 'Français (Suisse)', code: 'fr-ch' }, - { name: 'Frisian', code: 'fy' }, // TODO translate - { name: 'Gaeilge', code: 'ga' }, - { name: 'Gàidhlig', code: 'gd' }, - { name: 'Galego', code: 'gl' }, - { name: 'Konkani Devanagari', code: 'gom-deva' }, // TODO translate - { name: 'Konkani Latin', code: 'gom-latn' }, // TODO translate - { name: 'ગુજરાતી', code: 'gu' }, - { name: 'עברית', code: 'he' }, + // Spanish - Spain + { name: 'Español', code: 'es-ES' }, + // Spanish - Dominican Republic + { name: 'Español (República Dominicana)', code: 'es-DO' }, + // Spanish - Mexico + { name: 'Español (México)', code: 'es-MX' }, + // Spanish - United States + { name: 'Español (Estados Unidos)', code: 'es-US' }, + // Estonian - Estonia + { name: 'Eesti keel', code: 'et-EE' }, + // Basque - Basque Country + { name: 'Euskara', code: 'eu-ES' }, + // Persian - Iran + { name: 'فارسی', code: 'fa-IR' }, + // Filipino - Philippines + { name: 'Wikang Filipino', code: 'fil-PH' }, + // Finnish - Finland + { name: 'Suomi', code: 'fi-FI' }, + // Faroese - Faroe Islands + { name: 'Føroyskt', code: 'fo-FO' }, + // French - France + { name: 'Français', code: 'fr-FR' }, + // French - Canada + { name: 'Français (Canada)', code: 'fr-CA' }, + // French - Switzerland + { name: 'Français (Suisse)', code: 'fr-CH' }, + // West Frisian - Netherlands + { name: 'Frysk', code: 'fy' }, + // Irish - Ireland + { name: 'Gaeilge', code: 'ga-IE' }, + // Scottish Gaelic - Scotland (UK) + { name: 'Gàidhlig', code: 'gd-GB' }, + // Galician - Galicia (Spain) + { name: 'Galego', code: 'gl-ES' }, + // Konkani (Devanagari script) - India + { name: 'कोंकणी', code: 'gom-Deva' }, + // Konkani (Latin script) - India + { name: 'Konkani', code: 'gom-Latn' }, + // Gujarati - India + { name: 'ગુજરાતી', code: 'gu-IN' }, + // Hebrew - Israel + { name: 'עברית', code: 'he-IL' }, + // Hindi - India { name: 'हिन्दी', code: 'hi' }, - { name: 'Hrvatski', code: 'hr' }, - { name: 'Magyar', code: 'hu' }, - { name: 'Հայերեն', code: 'hy-am' }, - { name: 'Bahasa Indonesia', code: 'id' }, - { name: 'Íslenska', code: 'is' }, - { name: 'Italiano', code: 'it' }, - { name: 'Italiano (Switzerland)', code: 'it-ch' }, - { name: '日本語', code: 'ja' }, + // Croatian - Croatia + { name: 'Hrvatski jezik', code: 'hr' }, + // Hungarian - Hungary + { name: 'Magyar nyelv', code: 'hu-HU' }, + // Armenian - Armenia + { name: 'Հայերեն', code: 'hy-AM' }, + // Indonesian - Indonesia + { name: 'Bahasa Indonesia', code: 'id-ID' }, + // Icelandic - Iceland + { name: 'Íslenska', code: 'is-IS' }, + // Italian - Italy + { name: 'Italiano', code: 'it-IT' }, + // Italian - Switzerland + { name: 'Italiano (Svizzera)', code: 'it-CH' }, + // Japanese - Japan + { name: '日本語', code: 'ja-JP' }, + // Javanese - Indonesia { name: 'ꦧꦱꦗꦮ', code: 'jv' }, - { name: 'ქართული', code: 'ka' }, - { name: 'Қазақ Tілі', code: 'kk' }, - { name: 'Cambodian', code: 'km' }, // TODO translate - { name: 'ಕನ್ನಡ', code: 'kn' }, - { name: '한국어', code: 'ko' }, - { name: 'Kurdish', code: 'ku' }, // TODO translate - { name: 'Northern Kurdish', code: 'ku' }, // TODO translate - { name: 'Кыргыз тили', code: 'ky' }, - { name: 'Lëtzebuergesch', code: 'lb' }, - { name: 'ພາສາລາວ', code: 'lo' }, - { name: 'Lietuvių', code: 'lt' }, - { name: 'latviešu', code: 'lv' }, - { name: 'Mакедонски', code: 'mk' }, - { name: 'മലയാളം', code: 'ml' }, - { name: 'te Reo Māori', code: 'mi' }, - { name: 'crnogorski', code: 'me' }, + // Georgian - Georgia + { name: 'ქართული ენა', code: 'ka-GE' }, + // Kazakh - Kazakhstan + { name: 'Қазақ тілі', code: 'kk-KZ' }, + // Khmer - Cambodia + { name: 'ខ្មែរ', code: 'km-KH' }, + // Kannada - India + { name: 'ಕನ್ನಡ', code: 'kn-IN' }, + // Korean - South Korea + { name: '한국어', code: 'ko-KR' }, + // Kurdish - Kurdistan (Iraq, Iran, Syria, Turkey) + { name: 'Kurdî', code: 'ku' }, + // Kyrgyz - Kyrgyzstan + { name: 'Кыргыз тили', code: 'ky-KG' }, + // Luxembourgish - Luxembourg + { name: 'Lëtzebuergesch', code: 'lb-LU' }, + // Lao - Laos + { name: 'ພາສາລາວ', code: 'lo-LA' }, + // Lithuanian - Lithuania + { name: 'Lietuvių kalba', code: 'lt-LT' }, + // Latvian - Latvia + { name: 'Latviešu valoda', code: 'lv-LV' }, + // Macedonian - North Macedonia + { name: 'Македонски јазик', code: 'mk-MK' }, + // Malayalam - Kerala (India) + { name: 'മലയാളം', code: 'ml-IN' }, + // Māori - New Zealand + { name: 'Te Reo Māori', code: 'mi-NZ' }, + // Montenegrin - Montenegro + { name: 'Црногорски језик', code: 'cnr-ME' }, + // Marathi - Maharashtra (India) { name: 'मराठी', code: 'mr' }, + // Malay - Malaysia, Singapore, Brunei { name: 'Bahasa Melayu', code: 'ms' }, - { name: 'Malti', code: 'mt' }, - { name: 'Монгол Хэл', code: 'mn' }, - { name: 'Burmese', code: 'my' }, // TODO trasnlate: မြန်မာစာ ?? - { name: 'Norwegian Bokmål', code: 'nb' }, // TODO translate + // Maltese - Malta + { name: 'Malti', code: 'mt-MT' }, + // Mongolian - Mongolia + { name: 'Монгол хэл', code: 'mn-MN' }, + // Burmese - Myanmar + { name: 'မြန်မာစာ', code: 'my-MM' }, + // Norwegian Bokmål - Norway + { name: 'Norsk bokmål', code: 'nb' }, + // Nepali - Nepal and India { name: 'नेपाली', code: 'ne' }, - { name: 'Nederlands', code: 'nl' }, - { name: 'Nederlands (België)', code: 'nl-be' }, - { name: 'Ninorks', code: 'nn' }, //?? - { name: 'Occitan (Lengadocian)', code: 'oc-lnc' }, - { name: 'पंजाबी (ਭਾਰਤ)', code: 'pa-in' }, - { name: 'Polski', code: 'pl' }, - { name: 'Português', code: 'pt' }, - { name: 'Português (Brasil)', code: 'pt-br' }, - { name: 'Română', code: 'ro' }, - { name: 'Русский', code: 'ru' }, - { name: 'Nothern Sami', code: 'se' }, // TODO translate + // Dutch - Netherlands + { name: 'Nederlands', code: 'nl-NL' }, + // Dutch - Belgium (Flemish) + { name: 'Nederlands (België)', code: 'nl-BE' }, + // Norwegian Nynorsk - Norway + { name: 'Nynorsk', code: 'nn-NO' }, + // Occitan - Southern France, Monaco, Italy + { name: 'Occitan', code: 'oc' }, + // Punjabi - Punjab (India and Pakistan) + { name: 'ਪੰਜਾਬੀ', code: 'pa' }, + // Polish - Poland + { name: 'Polski', code: 'pl-PL' }, + // Portuguese - Portugal + { name: 'Português', code: 'pt-PT' }, + // Portuguese - Brazil + { name: 'Português (Brasil)', code: 'pt-BR' }, + // Romanian - Romania + { name: 'Română', code: 'ro-RO' }, + // Russian - Russia + { name: 'Русский язык', code: 'ru-RU' }, + // Northern Sami - Northern Scandinavia + { name: 'Davvisámegiella', code: 'se' }, + // Sindhi - Pakistan and India { name: 'سنڌي', code: 'sd' }, - { name: 'සිංහල', code: 'si' }, - { name: 'Slovenčina', code: 'sk' }, - { name: 'Slovenščina', code: 'sl' }, + // Sinhala - Sri Lanka + { name: 'සිංහල', code: 'si-LK' }, + // Slovak - Slovakia + { name: 'Slovenský jazyk', code: 'sk-SK' }, + // Slovenian - Slovenia + { name: 'Slovenski jezik', code: 'sl-SI' }, + // Albanian - Albania, Kosovo { name: 'Shqip', code: 'sq' }, + // Serbian - Serbia (Default) { name: 'Српски', code: 'sr' }, - { name: 'Serbian Cyrillic', code: 'sr-cyrl' }, // TODO translate - { name: 'siSwati', code: 'ss' }, + // Serbian - Serbia (Cyrillic script) + { name: 'Српски (ћирилица)', code: 'sr-Cyrl' }, + // Swati - Eswatini (Swaziland) + { name: 'SiSwati', code: 'ss' }, + // Swahili - East Africa { name: 'Kiswahili', code: 'sw' }, + // Swedish - Sweden { name: 'Svenska', code: 'sv' }, + // Tamil - Tamil Nadu (India), Sri Lanka, Singapore { name: 'தமிழ்', code: 'ta' }, + // Telugu - Andhra Pradesh, Telangana (India) { name: 'తెలుగు', code: 'te' }, - { name: 'Lia-Tetun', code: 'tet' }, + // Tetum - East Timor + { name: 'Tetun', code: 'tet' }, + // Tajik - Tajikistan { name: 'Тоҷикӣ', code: 'tg' }, - { name: 'ภาษาไทย', code: 'th' }, - { name: 'Türkmençe', code: 'tk' }, - { name: 'Tagalog (Philippines)', code: 'tl-ph' }, // TODO translate + // Thai - Thailand + { name: 'ภาษาไทย', code: 'th-TH' }, + // Turkmen - Turkmenistan + { name: 'Türkmen dili', code: 'tk-TM' }, + // Tagalog - Philippines + { name: 'Wikang Tagalog', code: 'tl-PH' }, + // Klingon - Constructed Language (Star Trek) { name: 'tlhIngan Hol', code: 'tlh' }, - { name: 'Türkçe', code: 'tr' }, - { name: 'Talossan', code: 'tzl' }, // TODO translate - { name: 'أمازيغية أطلس الأوسط', code: 'tzm' }, - { name: 'Central Atlas Tamazight Latin', code: 'tzm-latn' }, // TODO translate - { name: 'ئۇيغۇر تىلى', code: 'ug-cn' }, - { name: 'Українська', code: 'uk' }, - { name: 'اُردُو', code: 'ur' }, - { name: 'Ўзбек', code: 'uz' }, - { name: 'Uzbek (Latin)', code: 'uz-latn' }, // TODO translate - { name: 'tiếng Việt', code: 'vi' }, - { name: 'Chinese (China)', code: 'zh-cn' }, // TODO translate - { name: 'Chinese (Hong Kong)', code: 'zh-hk' }, // TODO translate - { name: 'Chinese (Macau)', code: 'zh-mo' }, // TODO translate - { name: 'Chinese (Taiwan)', code: 'zh-tw' }, // TODO translate - { name: 'Èdè Yorùbá', code: 'yo-ng' }, + // Turkish - Turkey + { name: 'Türkçe', code: 'tr-TR' }, + // Talossan - Constructed Language + { name: 'Talossan', code: 'tzl' }, + // Tamazight (Tifinagh script) - North Africa + { name: 'ⵜⴰⵎⴰⵣⵉⵖⵜ', code: 'tzm' }, + // Tamazight (Latin script) - North Africa + { name: 'Tamazight', code: 'tzm-Latn' }, + // Uyghur - Xinjiang (China) + { name: 'ئۇيغۇرچە', code: 'ug-CN' }, + // Ukrainian - Ukraine + { name: 'Українська мова', code: 'uk-UA' }, + // Urdu - Pakistan and India + { name: 'اردو', code: 'ur-PK' }, + // Uzbek - Uzbekistan (Cyrillic script) + { name: 'Ўзбек тили', code: 'uz-UZ' }, + // Uzbek - Uzbekistan (Latin script) + { name: "O'zbek tili", code: 'uz-Latn' }, + // Vietnamese - Vietnam + { name: 'Tiếng Việt', code: 'vi-VN' }, + // Chinese - China + { name: '中文', code: 'zh-CN' }, + // Chinese - Simplified + { name: '简体中文', code: 'zh-Hans' }, + // Chinese - Traditional + { name: '繁體中文', code: 'zh-Hant' }, + // Chinese - Hong Kong + { name: '中文 (香港)', code: 'zh-HK' }, + // Chinese - Taiwan + { name: '正體中文 (台灣)', code: 'zh-TW' }, + // Chinese - Macau + { name: '中文 (澳門)', code: 'zh-MO' }, + // Yoruba - Nigeria, Benin, Togo + { name: 'Yorùbá', code: 'yo' }, ]; From edeff686459ca43ace3a3d40084af7fa883dc3b0 Mon Sep 17 00:00:00 2001 From: Andres Martinez Gotor Date: Thu, 24 Apr 2025 12:00:32 +0200 Subject: [PATCH 082/146] Advisor: Allow to retry checks for a single element (#104279) --- apps/advisor/kinds/check.cue | 2 + .../apis/advisor/v0alpha1/check_status_gen.go | 2 + .../apis/advisor/v0alpha1/zz_openapi_gen.go | 10 ++- apps/advisor/pkg/apis/advisor_manifest.go | 2 +- apps/advisor/pkg/app/app.go | 15 ++++ .../pkg/app/checks/datasourcecheck/check.go | 14 ++++ apps/advisor/pkg/app/checks/ifaces.go | 2 + .../pkg/app/checks/plugincheck/check.go | 10 +++ .../pkg/app/checks/plugincheck/check_test.go | 21 ++--- apps/advisor/pkg/app/checks/utils.go | 7 ++ .../checktyperegisterer.go | 4 + apps/advisor/pkg/app/utils.go | 67 ++++++++++++++++ apps/advisor/pkg/app/utils_test.go | 76 +++++++++++++++++++ .../advisor.grafana.app-v0alpha1.json | 6 ++ .../app/api/clients/advisor/endpoints.gen.ts | 2 + public/app/api/clients/advisor/index.ts | 28 ++++++- 16 files changed, 255 insertions(+), 13 deletions(-) diff --git a/apps/advisor/kinds/check.cue b/apps/advisor/kinds/check.cue index 9ece12adcd2..755561ab615 100644 --- a/apps/advisor/kinds/check.cue +++ b/apps/advisor/kinds/check.cue @@ -36,6 +36,8 @@ check: { stepID: string // Human readable identifier of the item that failed item: string + // ID of the item that failed + itemID: string // Links to actions that can be taken to resolve the failure links: [...#ErrorLink] } diff --git a/apps/advisor/pkg/apis/advisor/v0alpha1/check_status_gen.go b/apps/advisor/pkg/apis/advisor/v0alpha1/check_status_gen.go index c9acc5ce31c..1658881108b 100644 --- a/apps/advisor/pkg/apis/advisor/v0alpha1/check_status_gen.go +++ b/apps/advisor/pkg/apis/advisor/v0alpha1/check_status_gen.go @@ -23,6 +23,8 @@ type CheckReportFailure struct { StepID string `json:"stepID"` // Human readable identifier of the item that failed Item string `json:"item"` + // ID of the item that failed + ItemID string `json:"itemID"` // Links to actions that can be taken to resolve the failure Links []CheckErrorLink `json:"links"` } diff --git a/apps/advisor/pkg/apis/advisor/v0alpha1/zz_openapi_gen.go b/apps/advisor/pkg/apis/advisor/v0alpha1/zz_openapi_gen.go index 79d78cd2766..bf1119a40e6 100644 --- a/apps/advisor/pkg/apis/advisor/v0alpha1/zz_openapi_gen.go +++ b/apps/advisor/pkg/apis/advisor/v0alpha1/zz_openapi_gen.go @@ -183,6 +183,14 @@ func schema_pkg_apis_advisor_v0alpha1_CheckReportFailure(ref common.ReferenceCal Format: "", }, }, + "itemID": { + SchemaProps: spec.SchemaProps{ + Description: "ID of the item that failed", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, "links": { SchemaProps: spec.SchemaProps{ Description: "Links to actions that can be taken to resolve the failure", @@ -198,7 +206,7 @@ func schema_pkg_apis_advisor_v0alpha1_CheckReportFailure(ref common.ReferenceCal }, }, }, - Required: []string{"severity", "stepID", "item", "links"}, + Required: []string{"severity", "stepID", "item", "itemID", "links"}, }, }, Dependencies: []string{ diff --git a/apps/advisor/pkg/apis/advisor_manifest.go b/apps/advisor/pkg/apis/advisor_manifest.go index e333774303f..73246227712 100644 --- a/apps/advisor/pkg/apis/advisor_manifest.go +++ b/apps/advisor/pkg/apis/advisor_manifest.go @@ -12,7 +12,7 @@ import ( ) var ( - rawSchemaCheckv0alpha1 = []byte(`{"spec":{"properties":{"data":{"additionalProperties":{"type":"string"},"description":"Generic data input that a check can receive","type":"object"}},"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"operatorStates":{"additionalProperties":{"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"description":"details contains any extra information that is operator-specific","type":"object","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"},"report":{"properties":{"count":{"description":"Number of elements analyzed","type":"integer"},"failures":{"description":"List of failures","items":{"properties":{"item":{"description":"Human readable identifier of the item that failed","type":"string"},"links":{"description":"Links to actions that can be taken to resolve the failure","items":{"properties":{"message":{"description":"Human readable error message","type":"string"},"url":{"description":"URL to a page with more information about the error","type":"string"}},"required":["url","message"],"type":"object"},"type":"array"},"severity":{"description":"Severity of the failure","enum":["high","low"],"type":"string"},"stepID":{"description":"Step ID that the failure is associated with","type":"string"}},"required":["severity","stepID","item","links"],"type":"object"},"type":"array"}},"required":["count","failures"],"type":"object"}},"required":["report"],"type":"object","x-kubernetes-preserve-unknown-fields":true}}`) + rawSchemaCheckv0alpha1 = []byte(`{"spec":{"properties":{"data":{"additionalProperties":{"type":"string"},"description":"Generic data input that a check can receive","type":"object"}},"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"operatorStates":{"additionalProperties":{"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"description":"details contains any extra information that is operator-specific","type":"object","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"},"report":{"properties":{"count":{"description":"Number of elements analyzed","type":"integer"},"failures":{"description":"List of failures","items":{"properties":{"item":{"description":"Human readable identifier of the item that failed","type":"string"},"itemID":{"description":"ID of the item that failed","type":"string"},"links":{"description":"Links to actions that can be taken to resolve the failure","items":{"properties":{"message":{"description":"Human readable error message","type":"string"},"url":{"description":"URL to a page with more information about the error","type":"string"}},"required":["url","message"],"type":"object"},"type":"array"},"severity":{"description":"Severity of the failure","enum":["high","low"],"type":"string"},"stepID":{"description":"Step ID that the failure is associated with","type":"string"}},"required":["severity","stepID","item","itemID","links"],"type":"object"},"type":"array"}},"required":["count","failures"],"type":"object"}},"required":["report"],"type":"object","x-kubernetes-preserve-unknown-fields":true}}`) versionSchemaCheckv0alpha1 app.VersionSchema _ = json.Unmarshal(rawSchemaCheckv0alpha1, &versionSchemaCheckv0alpha1) rawSchemaCheckTypev0alpha1 = []byte(`{"spec":{"properties":{"name":{"type":"string"},"steps":{"items":{"properties":{"description":{"type":"string"},"resolution":{"type":"string"},"stepID":{"type":"string"},"title":{"type":"string"}},"required":["title","description","stepID","resolution"],"type":"object"},"type":"array"}},"required":["name","steps"],"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"operatorStates":{"additionalProperties":{"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"description":"details contains any extra information that is operator-specific","type":"object","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object","x-kubernetes-preserve-unknown-fields":true}}`) diff --git a/apps/advisor/pkg/app/app.go b/apps/advisor/pkg/app/app.go index 5b34db5a090..5ce2128131a 100644 --- a/apps/advisor/pkg/app/app.go +++ b/apps/advisor/pkg/app/app.go @@ -74,6 +74,21 @@ func New(cfg app.Config) (app.App, error) { } }() } + if req.Action == resource.AdmissionActionUpdate { + go func() { + log.Debug("Updating check", "namespace", req.Object.GetNamespace(), "name", req.Object.GetName()) + requester, err := identity.GetRequester(ctx) + if err != nil { + log.Error("Error getting requester", "error", err) + return + } + ctx = identity.WithRequester(context.Background(), requester) + err = processCheckRetry(ctx, client, req.Object, check) + if err != nil { + log.Error("Error processing check retry", "error", err) + } + }() + } } return nil }, diff --git a/apps/advisor/pkg/app/checks/datasourcecheck/check.go b/apps/advisor/pkg/app/checks/datasourcecheck/check.go index 92a325f3211..64d34c3bfad 100644 --- a/apps/advisor/pkg/app/checks/datasourcecheck/check.go +++ b/apps/advisor/pkg/app/checks/datasourcecheck/check.go @@ -62,6 +62,17 @@ func (c *check) Items(ctx context.Context) ([]any, error) { return res, nil } +func (c *check) Item(ctx context.Context, id string) (any, error) { + requester, err := identity.GetRequester(ctx) + if err != nil { + return nil, err + } + return c.DatasourceSvc.GetDataSource(ctx, &datasources.GetDataSourceQuery{ + UID: id, + OrgID: requester.GetOrgID(), + }) +} + func (c *check) ID() string { return CheckID } @@ -113,6 +124,7 @@ func (s *uidValidationStep) Run(ctx context.Context, obj *advisor.CheckSpec, i a advisor.CheckReportFailureSeverityLow, s.ID(), fmt.Sprintf("%s (%s)", ds.Name, ds.UID), + ds.UID, []advisor.CheckErrorLink{}, ), nil } @@ -181,6 +193,7 @@ func (s *healthCheckStep) Run(ctx context.Context, obj *advisor.CheckSpec, i any advisor.CheckReportFailureSeverityHigh, s.ID(), ds.Name, + ds.UID, []advisor.CheckErrorLink{ { Message: "Fix me", @@ -241,6 +254,7 @@ func (s *missingPluginStep) Run(ctx context.Context, obj *advisor.CheckSpec, i a advisor.CheckReportFailureSeverityHigh, s.ID(), ds.Name, + ds.UID, links, ), nil } diff --git a/apps/advisor/pkg/app/checks/ifaces.go b/apps/advisor/pkg/app/checks/ifaces.go index 31456599381..0dfd13e4f61 100644 --- a/apps/advisor/pkg/app/checks/ifaces.go +++ b/apps/advisor/pkg/app/checks/ifaces.go @@ -10,6 +10,8 @@ import ( type Check interface { // ID returns the unique identifier of the check ID() string + // Item returns the item that will be checked + Item(ctx context.Context, id string) (any, error) // Items returns the list of items that will be checked Items(ctx context.Context) ([]any, error) // Steps returns the list of steps that will be executed diff --git a/apps/advisor/pkg/app/checks/plugincheck/check.go b/apps/advisor/pkg/app/checks/plugincheck/check.go index 7bb2b958ee0..b5d6edf09fe 100644 --- a/apps/advisor/pkg/app/checks/plugincheck/check.go +++ b/apps/advisor/pkg/app/checks/plugincheck/check.go @@ -61,6 +61,14 @@ func (c *check) Items(ctx context.Context) ([]any, error) { return res, nil } +func (c *check) Item(ctx context.Context, id string) (any, error) { + p, exists := c.PluginStore.Plugin(ctx, id) + if !exists { + return nil, fmt.Errorf("plugin %s not found", id) + } + return p, nil +} + func (c *check) Steps() []checks.Step { return []checks.Step{ &deprecationStep{ @@ -118,6 +126,7 @@ func (s *deprecationStep) Run(ctx context.Context, _ *advisor.CheckSpec, it any) return checks.NewCheckReportFailure( advisor.CheckReportFailureSeverityHigh, s.ID(), + p.Name, p.ID, []advisor.CheckErrorLink{ { @@ -190,6 +199,7 @@ func (s *updateStep) Run(ctx context.Context, _ *advisor.CheckSpec, i any) (*adv return checks.NewCheckReportFailure( advisor.CheckReportFailureSeverityLow, s.ID(), + p.Name, p.ID, []advisor.CheckErrorLink{ { diff --git a/apps/advisor/pkg/app/checks/plugincheck/check_test.go b/apps/advisor/pkg/app/checks/plugincheck/check_test.go index c8cdc965a32..34fd0b8e544 100644 --- a/apps/advisor/pkg/app/checks/plugincheck/check_test.go +++ b/apps/advisor/pkg/app/checks/plugincheck/check_test.go @@ -33,7 +33,7 @@ func TestRun(t *testing.T) { { name: "Deprecated plugin", plugins: []pluginstore.Plugin{ - {JSONData: plugins.JSONData{ID: "plugin1", Info: plugins.Info{Version: "1.0.0"}}}, + {JSONData: plugins.JSONData{ID: "plugin1", Name: "Plugin 1", Info: plugins.Info{Version: "1.0.0"}}}, }, pluginInfo: map[string]*repo.PluginInfo{ "plugin1": {Status: "deprecated"}, @@ -45,7 +45,8 @@ func TestRun(t *testing.T) { { Severity: advisor.CheckReportFailureSeverityHigh, StepID: "deprecation", - Item: "plugin1", + Item: "Plugin 1", + ItemID: "plugin1", Links: []advisor.CheckErrorLink{ { Url: "/plugins/plugin1", @@ -58,7 +59,7 @@ func TestRun(t *testing.T) { { name: "Plugin with update", plugins: []pluginstore.Plugin{ - {JSONData: plugins.JSONData{ID: "plugin2", Info: plugins.Info{Version: "1.0.0"}}}, + {JSONData: plugins.JSONData{ID: "plugin2", Name: "Plugin 2", Info: plugins.Info{Version: "1.0.0"}}}, }, pluginInfo: map[string]*repo.PluginInfo{ "plugin2": {Status: "active"}, @@ -70,7 +71,8 @@ func TestRun(t *testing.T) { { Severity: advisor.CheckReportFailureSeverityLow, StepID: "update", - Item: "plugin2", + Item: "Plugin 2", + ItemID: "plugin2", Links: []advisor.CheckErrorLink{ { Url: "/plugins/plugin2?page=version-history", @@ -83,7 +85,7 @@ func TestRun(t *testing.T) { { name: "Plugin with update (non semver)", plugins: []pluginstore.Plugin{ - {JSONData: plugins.JSONData{ID: "plugin2", Info: plugins.Info{Version: "alpha"}}}, + {JSONData: plugins.JSONData{ID: "plugin2", Name: "Plugin 2", Info: plugins.Info{Version: "alpha"}}}, }, pluginInfo: map[string]*repo.PluginInfo{ "plugin2": {Status: "active"}, @@ -95,7 +97,8 @@ func TestRun(t *testing.T) { { Severity: advisor.CheckReportFailureSeverityLow, StepID: "update", - Item: "plugin2", + Item: "Plugin 2", + ItemID: "plugin2", Links: []advisor.CheckErrorLink{ { Url: "/plugins/plugin2?page=version-history", @@ -108,7 +111,7 @@ func TestRun(t *testing.T) { { name: "Plugin pinned", plugins: []pluginstore.Plugin{ - {JSONData: plugins.JSONData{ID: "plugin3", Info: plugins.Info{Version: "1.0.0"}}}, + {JSONData: plugins.JSONData{ID: "plugin3", Name: "Plugin 3", Info: plugins.Info{Version: "1.0.0"}}}, }, pluginInfo: map[string]*repo.PluginInfo{ "plugin3": {Status: "active"}, @@ -122,7 +125,7 @@ func TestRun(t *testing.T) { { name: "Managed plugin", plugins: []pluginstore.Plugin{ - {JSONData: plugins.JSONData{ID: "plugin4", Info: plugins.Info{Version: "1.0.0"}}}, + {JSONData: plugins.JSONData{ID: "plugin4", Name: "Plugin 4", Info: plugins.Info{Version: "1.0.0"}}}, }, pluginInfo: map[string]*repo.PluginInfo{ "plugin4": {Status: "active"}, @@ -136,7 +139,7 @@ func TestRun(t *testing.T) { { name: "Provisioned plugin", plugins: []pluginstore.Plugin{ - {JSONData: plugins.JSONData{ID: "plugin5", Info: plugins.Info{Version: "1.0.0"}}}, + {JSONData: plugins.JSONData{ID: "plugin5", Name: "Plugin 5", Info: plugins.Info{Version: "1.0.0"}}}, }, pluginInfo: map[string]*repo.PluginInfo{ "plugin5": {Status: "active"}, diff --git a/apps/advisor/pkg/app/checks/utils.go b/apps/advisor/pkg/app/checks/utils.go index 8b4d23705f0..361f75b3d23 100644 --- a/apps/advisor/pkg/app/checks/utils.go +++ b/apps/advisor/pkg/app/checks/utils.go @@ -14,6 +14,7 @@ import ( const ( TypeLabel = "advisor.grafana.app/type" StatusAnnotation = "advisor.grafana.app/status" + RetryAnnotation = "advisor.grafana.app/retry" StatusAnnotationError = "error" StatusAnnotationProcessed = "processed" ) @@ -22,12 +23,14 @@ func NewCheckReportFailure( severity advisor.CheckReportFailureSeverity, stepID string, item string, + itemID string, links []advisor.CheckErrorLink, ) *advisor.CheckReportFailure { return &advisor.CheckReportFailure{ Severity: severity, StepID: stepID, Item: item, + ItemID: itemID, Links: links, } } @@ -47,6 +50,10 @@ func GetStatusAnnotation(obj resource.Object) string { return obj.GetAnnotations()[StatusAnnotation] } +func GetRetryAnnotation(obj resource.Object) string { + return obj.GetAnnotations()[RetryAnnotation] +} + func SetStatusAnnotation(ctx context.Context, client resource.Client, obj resource.Object, status string) error { annotations := obj.GetAnnotations() if annotations == nil { diff --git a/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer.go b/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer.go index 3e563b3f88f..f8c7e222368 100644 --- a/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer.go +++ b/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer.go @@ -94,6 +94,10 @@ func (r *Runner) Run(ctx context.Context) error { ObjectMeta: metav1.ObjectMeta{ Name: t.ID(), Namespace: r.namespace, + Annotations: map[string]string{ + // Flag to indicate feature availability + checks.RetryAnnotation: "1", + }, }, Spec: advisorv0alpha1.CheckTypeSpec{ Name: t.ID(), diff --git a/apps/advisor/pkg/app/utils.go b/apps/advisor/pkg/app/utils.go index d8a73935e34..82290b24020 100644 --- a/apps/advisor/pkg/app/utils.go +++ b/apps/advisor/pkg/app/utils.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "slices" "sync" "github.com/grafana/grafana-app-sdk/resource" @@ -76,6 +77,72 @@ func processCheck(ctx context.Context, client resource.Client, obj resource.Obje }, resource.PatchOptions{}, obj) } +func processCheckRetry(ctx context.Context, client resource.Client, obj resource.Object, check checks.Check) error { + status := checks.GetStatusAnnotation(obj) + if status == "" || status == checks.StatusAnnotationError { + // Check not processed yet or errored + return nil + } + // Get the item to retry from the annotation + itemToRetry := checks.GetRetryAnnotation(obj) + if itemToRetry == "" { + // No item to retry, nothing to do + return nil + } + c, ok := obj.(*advisorv0alpha1.Check) + if !ok { + return fmt.Errorf("invalid object type") + } + // Get the items to check + item, err := check.Item(ctx, itemToRetry) + if err != nil { + setErr := checks.SetStatusAnnotation(ctx, client, obj, checks.StatusAnnotationError) + if setErr != nil { + return setErr + } + return fmt.Errorf("error initializing check: %w", err) + } + // Run the steps + steps := check.Steps() + failures, err := runStepsInParallel(ctx, &c.Spec, steps, []any{item}) + if err != nil { + setErr := checks.SetStatusAnnotation(ctx, client, obj, checks.StatusAnnotationError) + if setErr != nil { + return setErr + } + return fmt.Errorf("error running steps: %w", err) + } + // Pull failures from the report for the items to retry + c.CheckStatus.Report.Failures = slices.DeleteFunc(c.CheckStatus.Report.Failures, func(f advisorv0alpha1.CheckReportFailure) bool { + if f.ItemID == itemToRetry { + for _, newFailure := range failures { + if newFailure.StepID == f.StepID { + // Same failure found, keep it + return false + } + } + // Failure no longer found, remove it + return true + } + // Failure not in the list of items to retry, keep it + return false + }) + // Delete the retry annotation to mark the check as processed + annotations := obj.GetAnnotations() + delete(annotations, checks.RetryAnnotation) + return client.PatchInto(ctx, obj.GetStaticMetadata().Identifier(), resource.PatchRequest{ + Operations: []resource.PatchOperation{{ + Operation: resource.PatchOpAdd, + Path: "/status/report", + Value: c.CheckStatus.Report, + }, { + Operation: resource.PatchOpAdd, + Path: "/metadata/annotations", + Value: annotations, + }}, + }, resource.PatchOptions{}, obj) +} + func runStepsInParallel(ctx context.Context, spec *advisorv0alpha1.CheckSpec, steps []checks.Step, items []any) ([]advisorv0alpha1.CheckReportFailure, error) { reportFailures := []advisorv0alpha1.CheckReportFailure{} var internalErr error diff --git a/apps/advisor/pkg/app/utils_test.go b/apps/advisor/pkg/app/utils_test.go index adcb2b8e28d..830afb5950e 100644 --- a/apps/advisor/pkg/app/utils_test.go +++ b/apps/advisor/pkg/app/utils_test.go @@ -129,6 +129,78 @@ func TestProcessCheck_RunError(t *testing.T) { assert.Equal(t, "error", obj.GetAnnotations()[checks.StatusAnnotation]) } +func TestProcessCheckRetry_NoRetry(t *testing.T) { + obj := &advisorv0alpha1.Check{} + obj.SetAnnotations(map[string]string{}) + meta, err := utils.MetaAccessor(obj) + if err != nil { + t.Fatal(err) + } + meta.SetCreatedBy("user:1") + client := &mockClient{} + ctx := context.TODO() + + check := &mockCheck{} + + err = processCheckRetry(ctx, client, obj, check) + assert.NoError(t, err) +} + +func TestProcessCheckRetry_RetryError(t *testing.T) { + obj := &advisorv0alpha1.Check{} + obj.SetAnnotations(map[string]string{ + checks.RetryAnnotation: "item", + checks.StatusAnnotation: "processed", + }) + meta, err := utils.MetaAccessor(obj) + if err != nil { + t.Fatal(err) + } + meta.SetCreatedBy("user:1") + client := &mockClient{} + ctx := context.TODO() + + check := &mockCheck{ + items: []any{"item"}, + err: errors.New("retry error"), + } + + err = processCheckRetry(ctx, client, obj, check) + assert.Error(t, err) + assert.Equal(t, "error", obj.GetAnnotations()[checks.StatusAnnotation]) +} + +func TestProcessCheckRetry_Success(t *testing.T) { + obj := &advisorv0alpha1.Check{} + obj.SetAnnotations(map[string]string{ + checks.RetryAnnotation: "item", + checks.StatusAnnotation: "processed", + }) + obj.CheckStatus.Report.Failures = []advisorv0alpha1.CheckReportFailure{ + { + ItemID: "item", + StepID: "step", + }, + } + meta, err := utils.MetaAccessor(obj) + if err != nil { + t.Fatal(err) + } + meta.SetCreatedBy("user:1") + client := &mockClient{} + ctx := context.TODO() + + check := &mockCheck{ + items: []any{"item"}, + } + + err = processCheckRetry(ctx, client, obj, check) + assert.NoError(t, err) + assert.Equal(t, "processed", obj.GetAnnotations()[checks.StatusAnnotation]) + assert.Empty(t, obj.GetAnnotations()[checks.RetryAnnotation]) + assert.Empty(t, obj.CheckStatus.Report.Failures) +} + type mockClient struct { resource.Client lastValue any @@ -152,6 +224,10 @@ func (m *mockCheck) Items(ctx context.Context) ([]any, error) { return m.items, nil } +func (m *mockCheck) Item(ctx context.Context, id string) (any, error) { + return m.items[0], nil +} + func (m *mockCheck) Steps() []checks.Step { return []checks.Step{ &mockStep{err: m.err}, diff --git a/pkg/tests/apis/openapi_snapshots/advisor.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/advisor.grafana.app-v0alpha1.json index 8ce087dae6e..7b2fad7ec8f 100644 --- a/pkg/tests/apis/openapi_snapshots/advisor.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/advisor.grafana.app-v0alpha1.json @@ -1825,6 +1825,7 @@ "severity", "stepID", "item", + "itemID", "links" ], "properties": { @@ -1833,6 +1834,11 @@ "type": "string", "default": "" }, + "itemID": { + "description": "ID of the item that failed", + "type": "string", + "default": "" + }, "links": { "description": "Links to actions that can be taken to resolve the failure", "type": "array", diff --git a/public/app/api/clients/advisor/endpoints.gen.ts b/public/app/api/clients/advisor/endpoints.gen.ts index c6c11e4f667..13fd3d849ec 100644 --- a/public/app/api/clients/advisor/endpoints.gen.ts +++ b/public/app/api/clients/advisor/endpoints.gen.ts @@ -355,6 +355,8 @@ export type CheckErrorLink = { export type CheckReportFailure = { /** Human readable identifier of the item that failed */ item: string; + /** ID of the item that failed */ + itemID: string; /** Links to actions that can be taken to resolve the failure */ links: CheckErrorLink[]; /** Severity of the failure */ diff --git a/public/app/api/clients/advisor/index.ts b/public/app/api/clients/advisor/index.ts index 9684976c51f..4f6f3177e85 100644 --- a/public/app/api/clients/advisor/index.ts +++ b/public/app/api/clients/advisor/index.ts @@ -1,4 +1,28 @@ import { generatedAPI } from './endpoints.gen'; -export const advisorAPI = generatedAPI; -export const { useGetCheckQuery, useCreateCheckMutation, useDeleteCheckMutation, useUpdateCheckMutation } = advisorAPI; +export const advisorAPI = generatedAPI.enhanceEndpoints({ + endpoints: { + // Need to mutate the generated query to set the Content-Type header correctly + updateCheck: (endpointDefinition) => { + const originalQuery = endpointDefinition.query; + if (originalQuery) { + endpointDefinition.query = (requestOptions) => ({ + ...originalQuery(requestOptions), + headers: { + 'Content-Type': 'application/json-patch+json', + }, + body: JSON.stringify(requestOptions.patch), + }); + } + }, + }, +}); +export const { + useGetCheckQuery, + useListCheckQuery, + useCreateCheckMutation, + useDeleteCheckMutation, + useUpdateCheckMutation, + useListCheckTypeQuery, +} = advisorAPI; +export { type Check, type CheckType } from './endpoints.gen'; // eslint-disable-line From d94a59cd08508de6dc91afaa274ef983a526b406 Mon Sep 17 00:00:00 2001 From: Fayzal Ghantiwala <114010985+fayzal-g@users.noreply.github.com> Date: Thu, 24 Apr 2025 12:28:28 +0100 Subject: [PATCH 083/146] Alerting: Fix flaky test (#104450) Fix flaky test --- pkg/services/ngalert/schedule/schedule_unit_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/services/ngalert/schedule/schedule_unit_test.go b/pkg/services/ngalert/schedule/schedule_unit_test.go index 2d9f1ad4af7..8b632e3942f 100644 --- a/pkg/services/ngalert/schedule/schedule_unit_test.go +++ b/pkg/services/ngalert/schedule/schedule_unit_test.go @@ -82,6 +82,7 @@ func TestProcessTicks(t *testing.T) { RecordingRulesCfg: rrSet, Tracer: testTracer, Log: log.New("ngalert.scheduler"), + FeatureToggles: featuremgmt.WithFeatures(), } managerCfg := state.ManagerCfg{ Metrics: testMetrics.GetStateMetrics(), From b4870964608c8442a9807183189b3d6dbfe40870 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Thu, 24 Apr 2025 12:42:52 +0100 Subject: [PATCH 084/146] Alerting: Fix empty state for mute timings (#104453) --- .../mute-timings/MuteTimingsTable.test.tsx | 13 ++++++- .../mute-timings/MuteTimingsTable.tsx | 38 ++++++++++--------- .../unified/mocks/server/configure.ts | 19 +++++++++- 3 files changed, 51 insertions(+), 19 deletions(-) diff --git a/public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.test.tsx b/public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.test.tsx index ff57ca0527a..94642688d1e 100644 --- a/public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.test.tsx +++ b/public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.test.tsx @@ -1,7 +1,10 @@ import { render, screen, userEvent, within } from 'test/test-utils'; import { setupMswServer } from 'app/features/alerting/unified/mockApi'; -import { setMuteTimingsListError } from 'app/features/alerting/unified/mocks/server/configure'; +import { + setMuteTimingsListError, + setTimeIntervalsListEmpty, +} from 'app/features/alerting/unified/mocks/server/configure'; import { setAlertmanagerConfig } from 'app/features/alerting/unified/mocks/server/entities/alertmanagers'; import { captureRequests } from 'app/features/alerting/unified/mocks/server/events'; import { AccessControlAction } from 'app/types'; @@ -89,6 +92,8 @@ describe('MuteTimingsTable', () => { expect(await screen.findByTestId('dynamic-table')).toBeInTheDocument(); expect(await screen.findByText('Provisioned')).toBeInTheDocument(); + expect(screen.queryByText(/no mute timings configured/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/you haven't created any mute timings yet/i)).not.toBeInTheDocument(); }); it('shows error when mute timings cannot load', async () => { @@ -114,6 +119,12 @@ describe('MuteTimingsTable', () => { expect(deleteRequest).toBeDefined(); }); + + it('shows empty state when no mute timings are configured', async () => { + setTimeIntervalsListEmpty(); + renderWithProvider(); + expect(await screen.findByText(/you haven't created any mute timings yet/i)).toBeInTheDocument(); + }); }); describe('without necessary permissions', () => { diff --git a/public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.tsx b/public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.tsx index e9fad0243e8..de41092bc25 100644 --- a/public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.tsx +++ b/public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.tsx @@ -108,24 +108,28 @@ export const MuteTimingsTable = () => { )} {items.length > 0 ? : null} - {items.length === 0 && !hideActions ? ( - + {!hideActions ? ( + + ) : ( + )} - buttonLabel="Add mute timing" - buttonIcon="plus" - buttonSize="lg" - href={makeAMLink('alerting/routes/mute-timing/new', alertManagerSourceName)} - showButton={allowedToCreateMuteTiming} - /> - ) : ( - + )}
); diff --git a/public/app/features/alerting/unified/mocks/server/configure.ts b/public/app/features/alerting/unified/mocks/server/configure.ts index b7efbcd53b5..ceed4471e0c 100644 --- a/public/app/features/alerting/unified/mocks/server/configure.ts +++ b/public/app/features/alerting/unified/mocks/server/configure.ts @@ -14,7 +14,11 @@ import { getDisabledPluginHandler, getPluginMissingHandler, } from 'app/features/alerting/unified/mocks/server/handlers/plugins'; -import { ALERTING_API_SERVER_BASE_URL, paginatedHandlerFor } from 'app/features/alerting/unified/mocks/server/utils'; +import { + ALERTING_API_SERVER_BASE_URL, + getK8sResponse, + paginatedHandlerFor, +} from 'app/features/alerting/unified/mocks/server/utils'; import { SupportedPlugin } from 'app/features/alerting/unified/types/pluginBridges'; import { clearPluginSettingsCache } from 'app/features/plugins/pluginSettings'; import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types'; @@ -158,6 +162,19 @@ export const setMuteTimingsListError = () => { return handler; }; +/** + * Makes the mock server respond with no time intervals + */ +export const setTimeIntervalsListEmpty = () => { + const listMuteTimingsPath = listNamespacedTimeIntervalHandler().info.path; + const handler = http.get(listMuteTimingsPath, () => { + return HttpResponse.json(getK8sResponse('TimeIntervalList', [])); + }); + + server.use(handler); + return handler; +}; + export function mimirDataSource() { const dataSource = mockDataSource( { From 1ffd64ff795e2b79d5a615abacee58d7dfa252dd Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Thu, 24 Apr 2025 13:47:24 +0200 Subject: [PATCH 085/146] =?UTF-8?q?Alerting:=20Package=20=F0=9F=93=A6=20(#?= =?UTF-8?q?102899)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/CODEOWNERS | 1 + package.json | 1 + packages/grafana-alerting/CHANGELOG.md | 0 packages/grafana-alerting/LICENSE_APACHE2 | 202 +++ packages/grafana-alerting/README.md | 9 + packages/grafana-alerting/package.json | 63 + packages/grafana-alerting/project.json | 6 + packages/grafana-alerting/scripts/codegen.ts | 21 + .../grafana-alerting/src/grafana/api.gen.ts | 1602 +++++++++++++++++ packages/grafana-alerting/src/grafana/api.ts | 11 + .../components/ContactPointSelector.tsx | 50 + .../contactPoints/hooks/useContactPoints.tsx | 29 + .../src/grafana/contactPoints/types.ts | 76 + .../src/grafana/contactPoints/utils.ts | 31 + packages/grafana-alerting/src/index.ts | 9 + packages/grafana-alerting/src/internal.ts | 6 + packages/grafana-alerting/src/unstable.ts | 8 + packages/grafana-alerting/tsconfig.build.json | 4 + packages/grafana-alerting/tsconfig.json | 12 + yarn.lock | 409 ++++- 20 files changed, 2453 insertions(+), 97 deletions(-) create mode 100644 packages/grafana-alerting/CHANGELOG.md create mode 100644 packages/grafana-alerting/LICENSE_APACHE2 create mode 100644 packages/grafana-alerting/README.md create mode 100644 packages/grafana-alerting/package.json create mode 100644 packages/grafana-alerting/project.json create mode 100644 packages/grafana-alerting/scripts/codegen.ts create mode 100644 packages/grafana-alerting/src/grafana/api.gen.ts create mode 100644 packages/grafana-alerting/src/grafana/api.ts create mode 100644 packages/grafana-alerting/src/grafana/contactPoints/components/ContactPointSelector.tsx create mode 100644 packages/grafana-alerting/src/grafana/contactPoints/hooks/useContactPoints.tsx create mode 100644 packages/grafana-alerting/src/grafana/contactPoints/types.ts create mode 100644 packages/grafana-alerting/src/grafana/contactPoints/utils.ts create mode 100644 packages/grafana-alerting/src/index.ts create mode 100644 packages/grafana-alerting/src/internal.ts create mode 100644 packages/grafana-alerting/src/unstable.ts create mode 100644 packages/grafana-alerting/tsconfig.build.json create mode 100644 packages/grafana-alerting/tsconfig.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 67ceca61366..23e9fe99a3f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -435,6 +435,7 @@ /packages/grafana-ui/src/graveyard/GraphNG/ @grafana/dataviz-squad /packages/grafana-ui/src/graveyard/TimeSeries/ @grafana/dataviz-squad /packages/grafana-ui/src/utils/storybook/ @grafana/grafana-frontend-platform +/packages/grafana-alerting/ @grafana/alerting-frontend # root files, mostly frontend /.browserslistrc @grafana/frontend-ops diff --git a/package.json b/package.json index 226898e3351..a117c9a4e47 100644 --- a/package.json +++ b/package.json @@ -258,6 +258,7 @@ "@floating-ui/react": "0.27.7", "@formatjs/intl-durationformat": "^0.7.0", "@glideapps/glide-data-grid": "^6.0.0", + "@grafana/alerting": "workspace:*", "@grafana/aws-sdk": "0.6.0", "@grafana/azure-sdk": "0.0.7", "@grafana/data": "workspace:*", diff --git a/packages/grafana-alerting/CHANGELOG.md b/packages/grafana-alerting/CHANGELOG.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/grafana-alerting/LICENSE_APACHE2 b/packages/grafana-alerting/LICENSE_APACHE2 new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/packages/grafana-alerting/LICENSE_APACHE2 @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/grafana-alerting/README.md b/packages/grafana-alerting/README.md new file mode 100644 index 00000000000..94284bad290 --- /dev/null +++ b/packages/grafana-alerting/README.md @@ -0,0 +1,9 @@ +# Grafana Alerting + +> **@grafana/alerting is currently in ALPHA**. + +This package is a collection of types, libraries, utilities, React components and hooks for interacting with the Grafana alerting system and is meant to be the foundation of alerting-related features in Grafana and its plugins. + +We plan to eventually publish this package on NPM; however, it is currently in ALPHA and is not yet ready for public consumption. We are actively working on this package and will be making breaking changes to it as we iterate on the design and implementation. + +Once we've settled on a public API that we feel is stable and useful, we will publish this package on NPM and provide documentation on how to use it. diff --git a/packages/grafana-alerting/package.json b/packages/grafana-alerting/package.json new file mode 100644 index 00000000000..b52ef4b1139 --- /dev/null +++ b/packages/grafana-alerting/package.json @@ -0,0 +1,63 @@ +{ + "author": "Grafana Labs", + "license": "Apache-2.0", + "name": "@grafana/alerting", + "version": "12.0.0-pre", + "private": true, + "description": "Grafana Alerting Library – Build vertical integrations on top of the industry-leading alerting solution", + "keywords": [ + "typescript", + "grafana", + "alerting", + "alertmanager", + "prometheus" + ], + "sideEffects": false, + "repository": { + "type": "git", + "url": "http://github.com/grafana/grafana.git", + "directory": "packages/grafana-alerting" + }, + "main": "src/index.ts", + "types": "src/index.ts", + "module": "src/index.ts", + "exports": { + ".": { + "import": "./src/index.ts", + "require": "./src/index.ts" + }, + "./internal": { + "import": "./src/internal.ts", + "require": "./src/internal.ts" + }, + "./unstable": { + "import": "./src/unstable.ts", + "require": "./src/unstable.ts" + } + }, + "scripts": { + "typecheck": "tsc --emitDeclarationOnly false --noEmit", + "codegen": "yarn run rtk-query-codegen-openapi ./scripts/codegen.ts" + }, + "devDependencies": { + "@grafana/tsconfig": "^2.0.0", + "@rtk-query/codegen-openapi": "^2.0.0", + "@types/lodash": "^4", + "@types/react": "18.3.18", + "@types/react-dom": "18.3.5", + "react": "18.3.1", + "react-dom": "18.3.1", + "type-fest": "^4.40.0", + "typescript": "5.7.3" + }, + "peerDependencies": { + "@grafana/runtime": "^12.0.0-pre", + "@grafana/ui": "^12.0.0-pre", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "dependencies": { + "@reduxjs/toolkit": "^2.7.0", + "lodash": "^4.17.21" + } +} diff --git a/packages/grafana-alerting/project.json b/packages/grafana-alerting/project.json new file mode 100644 index 00000000000..2fe46a23eff --- /dev/null +++ b/packages/grafana-alerting/project.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "projectType": "library", + "tags": ["scope:package", "type:ui"], + "targets": {} +} diff --git a/packages/grafana-alerting/scripts/codegen.ts b/packages/grafana-alerting/scripts/codegen.ts new file mode 100644 index 00000000000..1af5010092f --- /dev/null +++ b/packages/grafana-alerting/scripts/codegen.ts @@ -0,0 +1,21 @@ +/** + * This script will generate TypeScript type definitions and a RTKQ client for the alerting k8s APIs. + * It downloads the OpenAPI schema from a running Grafana instance and generates the types. + * + * Run `yarn run codegen` from the "grafana-alerting" package to invoke this script. + */ +import { type ConfigFile } from '@rtk-query/codegen-openapi'; +import { resolve } from 'node:path'; + +// these snapshots are generated by running "go test pkg/tests/apis/openapi_test.go", see the README in the "openapi_snapshots" directory +const OPENAPI_SCHEMA_LOCATION = resolve( + '../../../pkg/tests/apis/openapi_snapshots/notifications.alerting.grafana.app-v0alpha1.json' +); + +export default { + exportName: 'alertingAPI', + schemaFile: OPENAPI_SCHEMA_LOCATION, + apiFile: '../src/grafana/api.ts', + outputFile: resolve('../src/grafana/api.gen.ts'), + tag: true, +} satisfies ConfigFile; diff --git a/packages/grafana-alerting/src/grafana/api.gen.ts b/packages/grafana-alerting/src/grafana/api.gen.ts new file mode 100644 index 00000000000..dd6ebf30830 --- /dev/null +++ b/packages/grafana-alerting/src/grafana/api.gen.ts @@ -0,0 +1,1602 @@ +import { api } from './api'; +export const addTagTypes = ['API Discovery', 'Receiver', 'RoutingTree', 'TemplateGroup', 'TimeInterval'] as const; +const injectedRtkApi = api + .enhanceEndpoints({ + addTagTypes, + }) + .injectEndpoints({ + endpoints: (build) => ({ + getApiResources: build.query({ + query: () => ({ url: `/apis/notifications.alerting.grafana.app/v0alpha1/` }), + providesTags: ['API Discovery'], + }), + listReceiver: build.query({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/receivers`, + params: { + pretty: queryArg.pretty, + allowWatchBookmarks: queryArg.allowWatchBookmarks, + continue: queryArg['continue'], + fieldSelector: queryArg.fieldSelector, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + watch: queryArg.watch, + }, + }), + providesTags: ['Receiver'], + }), + createReceiver: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/receivers`, + method: 'POST', + body: queryArg.comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['Receiver'], + }), + deletecollectionReceiver: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/receivers`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + continue: queryArg['continue'], + dryRun: queryArg.dryRun, + fieldSelector: queryArg.fieldSelector, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + }, + }), + invalidatesTags: ['Receiver'], + }), + getReceiver: build.query({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/receivers/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['Receiver'], + }), + replaceReceiver: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/receivers/${queryArg.name}`, + method: 'PUT', + body: queryArg.comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['Receiver'], + }), + deleteReceiver: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/receivers/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['Receiver'], + }), + updateReceiver: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/receivers/${queryArg.name}`, + method: 'PATCH', + body: queryArg.ioK8SApimachineryPkgApisMetaV1Patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['Receiver'], + }), + listRoutingTree: build.query({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/routingtrees`, + params: { + pretty: queryArg.pretty, + allowWatchBookmarks: queryArg.allowWatchBookmarks, + continue: queryArg['continue'], + fieldSelector: queryArg.fieldSelector, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + watch: queryArg.watch, + }, + }), + providesTags: ['RoutingTree'], + }), + createRoutingTree: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/routingtrees`, + method: 'POST', + body: queryArg.comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['RoutingTree'], + }), + deletecollectionRoutingTree: build.mutation< + DeletecollectionRoutingTreeApiResponse, + DeletecollectionRoutingTreeApiArg + >({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/routingtrees`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + continue: queryArg['continue'], + dryRun: queryArg.dryRun, + fieldSelector: queryArg.fieldSelector, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + }, + }), + invalidatesTags: ['RoutingTree'], + }), + getRoutingTree: build.query({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/routingtrees/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['RoutingTree'], + }), + replaceRoutingTree: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/routingtrees/${queryArg.name}`, + method: 'PUT', + body: queryArg.comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['RoutingTree'], + }), + deleteRoutingTree: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/routingtrees/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['RoutingTree'], + }), + updateRoutingTree: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/routingtrees/${queryArg.name}`, + method: 'PATCH', + body: queryArg.ioK8SApimachineryPkgApisMetaV1Patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['RoutingTree'], + }), + listTemplateGroup: build.query({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/templategroups`, + params: { + pretty: queryArg.pretty, + allowWatchBookmarks: queryArg.allowWatchBookmarks, + continue: queryArg['continue'], + fieldSelector: queryArg.fieldSelector, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + watch: queryArg.watch, + }, + }), + providesTags: ['TemplateGroup'], + }), + createTemplateGroup: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/templategroups`, + method: 'POST', + body: queryArg.comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['TemplateGroup'], + }), + deletecollectionTemplateGroup: build.mutation< + DeletecollectionTemplateGroupApiResponse, + DeletecollectionTemplateGroupApiArg + >({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/templategroups`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + continue: queryArg['continue'], + dryRun: queryArg.dryRun, + fieldSelector: queryArg.fieldSelector, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + }, + }), + invalidatesTags: ['TemplateGroup'], + }), + getTemplateGroup: build.query({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/templategroups/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['TemplateGroup'], + }), + replaceTemplateGroup: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/templategroups/${queryArg.name}`, + method: 'PUT', + body: queryArg.comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['TemplateGroup'], + }), + deleteTemplateGroup: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/templategroups/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['TemplateGroup'], + }), + updateTemplateGroup: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/templategroups/${queryArg.name}`, + method: 'PATCH', + body: queryArg.ioK8SApimachineryPkgApisMetaV1Patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['TemplateGroup'], + }), + listTimeInterval: build.query({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/timeintervals`, + params: { + pretty: queryArg.pretty, + allowWatchBookmarks: queryArg.allowWatchBookmarks, + continue: queryArg['continue'], + fieldSelector: queryArg.fieldSelector, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + watch: queryArg.watch, + }, + }), + providesTags: ['TimeInterval'], + }), + createTimeInterval: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/timeintervals`, + method: 'POST', + body: queryArg.comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['TimeInterval'], + }), + deletecollectionTimeInterval: build.mutation< + DeletecollectionTimeIntervalApiResponse, + DeletecollectionTimeIntervalApiArg + >({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/timeintervals`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + continue: queryArg['continue'], + dryRun: queryArg.dryRun, + fieldSelector: queryArg.fieldSelector, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + }, + }), + invalidatesTags: ['TimeInterval'], + }), + getTimeInterval: build.query({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/timeintervals/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['TimeInterval'], + }), + replaceTimeInterval: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/timeintervals/${queryArg.name}`, + method: 'PUT', + body: queryArg.comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['TimeInterval'], + }), + deleteTimeInterval: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/timeintervals/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['TimeInterval'], + }), + updateTimeInterval: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/timeintervals/${queryArg.name}`, + method: 'PATCH', + body: queryArg.ioK8SApimachineryPkgApisMetaV1Patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['TimeInterval'], + }), + }), + overrideExisting: false, + }); +export { injectedRtkApi as alertingAPI }; +export type GetApiResourcesApiResponse = /** status 200 OK */ IoK8SApimachineryPkgApisMetaV1ApiResourceList; +export type GetApiResourcesApiArg = void; +export type ListReceiverApiResponse = + /** status 200 OK */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1ReceiverList; +export type ListReceiverApiArg = { + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** allowWatchBookmarks requests watch events with type "BOOKMARK". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored. */ + allowWatchBookmarks?: boolean; + /** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key". + + This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */ + continue?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true. + + The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */ + limit?: number; + /** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersion?: string; + /** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; + /** Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion. */ + watch?: boolean; +}; +export type CreateReceiverApiResponse = /** status 200 OK */ + | ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver + | /** status 201 Created */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver + | /** status 202 Accepted */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver; +export type CreateReceiverApiArg = { + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver; +}; +export type DeletecollectionReceiverApiResponse = /** status 200 OK */ IoK8SApimachineryPkgApisMetaV1Status; +export type DeletecollectionReceiverApiArg = { + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key". + + This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */ + continue?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */ + gracePeriodSeconds?: number; + /** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true. + + The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */ + limit?: number; + /** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; + /** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersion?: string; + /** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; +}; +export type GetReceiverApiResponse = + /** status 200 OK */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver; +export type GetReceiverApiArg = { + /** name of the Receiver */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; +}; +export type ReplaceReceiverApiResponse = /** status 200 OK */ + | ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver + | /** status 201 Created */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver; +export type ReplaceReceiverApiArg = { + /** name of the Receiver */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver; +}; +export type DeleteReceiverApiResponse = /** status 200 OK */ + | IoK8SApimachineryPkgApisMetaV1Status + | /** status 202 Accepted */ IoK8SApimachineryPkgApisMetaV1Status; +export type DeleteReceiverApiArg = { + /** name of the Receiver */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */ + gracePeriodSeconds?: number; + /** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; +}; +export type UpdateReceiverApiResponse = /** status 200 OK */ + | ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver + | /** status 201 Created */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver; +export type UpdateReceiverApiArg = { + /** name of the Receiver */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch). */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + /** Force is going to "force" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests. */ + force?: boolean; + ioK8SApimachineryPkgApisMetaV1Patch: IoK8SApimachineryPkgApisMetaV1Patch; +}; +export type ListRoutingTreeApiResponse = + /** status 200 OK */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTreeList; +export type ListRoutingTreeApiArg = { + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** allowWatchBookmarks requests watch events with type "BOOKMARK". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored. */ + allowWatchBookmarks?: boolean; + /** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key". + + This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */ + continue?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true. + + The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */ + limit?: number; + /** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersion?: string; + /** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; + /** Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion. */ + watch?: boolean; +}; +export type CreateRoutingTreeApiResponse = /** status 200 OK */ + | ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree + | /** status 201 Created */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree + | /** status 202 Accepted */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree; +export type CreateRoutingTreeApiArg = { + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree; +}; +export type DeletecollectionRoutingTreeApiResponse = /** status 200 OK */ IoK8SApimachineryPkgApisMetaV1Status; +export type DeletecollectionRoutingTreeApiArg = { + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key". + + This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */ + continue?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */ + gracePeriodSeconds?: number; + /** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true. + + The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */ + limit?: number; + /** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; + /** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersion?: string; + /** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; +}; +export type GetRoutingTreeApiResponse = + /** status 200 OK */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree; +export type GetRoutingTreeApiArg = { + /** name of the RoutingTree */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; +}; +export type ReplaceRoutingTreeApiResponse = /** status 200 OK */ + | ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree + | /** status 201 Created */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree; +export type ReplaceRoutingTreeApiArg = { + /** name of the RoutingTree */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree; +}; +export type DeleteRoutingTreeApiResponse = /** status 200 OK */ + | IoK8SApimachineryPkgApisMetaV1Status + | /** status 202 Accepted */ IoK8SApimachineryPkgApisMetaV1Status; +export type DeleteRoutingTreeApiArg = { + /** name of the RoutingTree */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */ + gracePeriodSeconds?: number; + /** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; +}; +export type UpdateRoutingTreeApiResponse = /** status 200 OK */ + | ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree + | /** status 201 Created */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree; +export type UpdateRoutingTreeApiArg = { + /** name of the RoutingTree */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch). */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + /** Force is going to "force" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests. */ + force?: boolean; + ioK8SApimachineryPkgApisMetaV1Patch: IoK8SApimachineryPkgApisMetaV1Patch; +}; +export type ListTemplateGroupApiResponse = + /** status 200 OK */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroupList; +export type ListTemplateGroupApiArg = { + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** allowWatchBookmarks requests watch events with type "BOOKMARK". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored. */ + allowWatchBookmarks?: boolean; + /** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key". + + This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */ + continue?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true. + + The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */ + limit?: number; + /** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersion?: string; + /** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; + /** Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion. */ + watch?: boolean; +}; +export type CreateTemplateGroupApiResponse = /** status 200 OK */ + | ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup + | /** status 201 Created */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup + | /** status 202 Accepted */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup; +export type CreateTemplateGroupApiArg = { + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup; +}; +export type DeletecollectionTemplateGroupApiResponse = /** status 200 OK */ IoK8SApimachineryPkgApisMetaV1Status; +export type DeletecollectionTemplateGroupApiArg = { + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key". + + This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */ + continue?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */ + gracePeriodSeconds?: number; + /** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true. + + The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */ + limit?: number; + /** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; + /** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersion?: string; + /** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; +}; +export type GetTemplateGroupApiResponse = + /** status 200 OK */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup; +export type GetTemplateGroupApiArg = { + /** name of the TemplateGroup */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; +}; +export type ReplaceTemplateGroupApiResponse = /** status 200 OK */ + | ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup + | /** status 201 Created */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup; +export type ReplaceTemplateGroupApiArg = { + /** name of the TemplateGroup */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup; +}; +export type DeleteTemplateGroupApiResponse = /** status 200 OK */ + | IoK8SApimachineryPkgApisMetaV1Status + | /** status 202 Accepted */ IoK8SApimachineryPkgApisMetaV1Status; +export type DeleteTemplateGroupApiArg = { + /** name of the TemplateGroup */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */ + gracePeriodSeconds?: number; + /** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; +}; +export type UpdateTemplateGroupApiResponse = /** status 200 OK */ + | ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup + | /** status 201 Created */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup; +export type UpdateTemplateGroupApiArg = { + /** name of the TemplateGroup */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch). */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + /** Force is going to "force" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests. */ + force?: boolean; + ioK8SApimachineryPkgApisMetaV1Patch: IoK8SApimachineryPkgApisMetaV1Patch; +}; +export type ListTimeIntervalApiResponse = + /** status 200 OK */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeIntervalList; +export type ListTimeIntervalApiArg = { + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** allowWatchBookmarks requests watch events with type "BOOKMARK". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored. */ + allowWatchBookmarks?: boolean; + /** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key". + + This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */ + continue?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true. + + The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */ + limit?: number; + /** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersion?: string; + /** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; + /** Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion. */ + watch?: boolean; +}; +export type CreateTimeIntervalApiResponse = /** status 200 OK */ + | ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval + | /** status 201 Created */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval + | /** status 202 Accepted */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval; +export type CreateTimeIntervalApiArg = { + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval; +}; +export type DeletecollectionTimeIntervalApiResponse = /** status 200 OK */ IoK8SApimachineryPkgApisMetaV1Status; +export type DeletecollectionTimeIntervalApiArg = { + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key". + + This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */ + continue?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */ + gracePeriodSeconds?: number; + /** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true. + + The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */ + limit?: number; + /** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; + /** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersion?: string; + /** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; +}; +export type GetTimeIntervalApiResponse = + /** status 200 OK */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval; +export type GetTimeIntervalApiArg = { + /** name of the TimeInterval */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; +}; +export type ReplaceTimeIntervalApiResponse = /** status 200 OK */ + | ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval + | /** status 201 Created */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval; +export type ReplaceTimeIntervalApiArg = { + /** name of the TimeInterval */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval; +}; +export type DeleteTimeIntervalApiResponse = /** status 200 OK */ + | IoK8SApimachineryPkgApisMetaV1Status + | /** status 202 Accepted */ IoK8SApimachineryPkgApisMetaV1Status; +export type DeleteTimeIntervalApiArg = { + /** name of the TimeInterval */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */ + gracePeriodSeconds?: number; + /** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; +}; +export type UpdateTimeIntervalApiResponse = /** status 200 OK */ + | ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval + | /** status 201 Created */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval; +export type UpdateTimeIntervalApiArg = { + /** name of the TimeInterval */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch). */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + /** Force is going to "force" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests. */ + force?: boolean; + ioK8SApimachineryPkgApisMetaV1Patch: IoK8SApimachineryPkgApisMetaV1Patch; +}; +export type IoK8SApimachineryPkgApisMetaV1ApiResource = { + /** categories is a list of the grouped resources this resource belongs to (e.g. 'all') */ + categories?: string[]; + /** group is the preferred group of the resource. Empty implies the group of the containing resource list. For subresources, this may have a different value, for example: Scale". */ + group?: string; + /** kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo') */ + kind: string; + /** name is the plural name of the resource. */ + name: string; + /** namespaced indicates if a resource is namespaced or not. */ + namespaced: boolean; + /** shortNames is a list of suggested short names of the resource. */ + shortNames?: string[]; + /** singularName is the singular name of the resource. This allows clients to handle plural and singular opaquely. The singularName is more correct for reporting status on a single item and both singular and plural are allowed from the kubectl CLI interface. */ + singularName: string; + /** The hash value of the storage version, the version this resource is converted to when written to the data store. Value must be treated as opaque by clients. Only equality comparison on the value is valid. This is an alpha feature and may change or be removed in the future. The field is populated by the apiserver only if the StorageVersionHash feature gate is enabled. This field will remain optional even if it graduates. */ + storageVersionHash?: string; + /** verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy) */ + verbs: string[]; + /** version is the preferred version of the resource. Empty implies the version of the containing resource list For subresources, this may have a different value, for example: v1 (while inside a v1beta1 version of the core resource's group)". */ + version?: string; +}; +export type IoK8SApimachineryPkgApisMetaV1ApiResourceList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** groupVersion is the group and version this APIResourceList is for. */ + groupVersion: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + /** resources contains the name of the resources and if they are namespaced. */ + resources: IoK8SApimachineryPkgApisMetaV1ApiResource[]; +}; +export type IoK8SApimachineryPkgApisMetaV1Time = string; +export type IoK8SApimachineryPkgApisMetaV1FieldsV1 = object; +export type IoK8SApimachineryPkgApisMetaV1ManagedFieldsEntry = { + /** APIVersion defines the version of this resource that this field set applies to. The format is "group/version" just like the top-level APIVersion field. It is necessary to track the version of a field set because it cannot be automatically converted. */ + apiVersion?: string; + /** FieldsType is the discriminator for the different fields format and version. There is currently only one possible value: "FieldsV1" */ + fieldsType?: string; + /** FieldsV1 holds the first JSON version format as described in the "FieldsV1" type. */ + fieldsV1?: IoK8SApimachineryPkgApisMetaV1FieldsV1; + /** Manager is an identifier of the workflow managing these fields. */ + manager?: string; + /** Operation is the type of operation which lead to this ManagedFieldsEntry being created. The only valid values for this field are 'Apply' and 'Update'. */ + operation?: string; + /** Subresource is the name of the subresource used to update that object, or empty string if the object was updated through the main resource. The value of this field is used to distinguish between managers, even if they share the same name. For example, a status update will be distinct from a regular update using the same manager name. Note that the APIVersion field is not related to the Subresource field and it always corresponds to the version of the main resource. */ + subresource?: string; + /** Time is the timestamp of when the ManagedFields entry was added. The timestamp will also be updated if a field is added, the manager changes any of the owned fields value or removes a field. The timestamp does not update when a field is removed from the entry because another manager took it over. */ + time?: IoK8SApimachineryPkgApisMetaV1Time; +}; +export type IoK8SApimachineryPkgApisMetaV1OwnerReference = { + /** API version of the referent. */ + apiVersion: string; + /** If true, AND if the owner has the "foregroundDeletion" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed. See https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion for how the garbage collector interacts with this field and enforces the foreground deletion. Defaults to false. To set this field, a user needs "delete" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned. */ + blockOwnerDeletion?: boolean; + /** If true, this reference points to the managing controller. */ + controller?: boolean; + /** Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind: string; + /** Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names */ + name: string; + /** UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids */ + uid: string; +}; +export type IoK8SApimachineryPkgApisMetaV1ObjectMeta = { + /** Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations */ + annotations?: { + [key: string]: string; + }; + /** CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. + + Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata */ + creationTimestamp?: IoK8SApimachineryPkgApisMetaV1Time; + /** Number of seconds allowed for this object to gracefully terminate before it will be removed from the system. Only set when deletionTimestamp is also set. May only be shortened. Read-only. */ + deletionGracePeriodSeconds?: number; + /** DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This field is set by the server when a graceful deletion is requested by the user, and is not directly settable by a client. The resource is expected to be deleted (no longer visible from resource lists, and not reachable by name) after the time in this field, once the finalizers list is empty. As long as the finalizers list contains items, deletion is blocked. Once the deletionTimestamp is set, this value may not be unset or be set further into the future, although it may be shortened or the resource may be deleted prior to this time. For example, a user may request that a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination signal to the containers in the pod. After that 30 seconds, the Kubelet will send a hard termination signal (SIGKILL) to the container and after cleanup, remove the pod from the API. In the presence of network partitions, this object may still exist after this timestamp, until an administrator or automated process can determine the resource is fully terminated. If not set, graceful deletion of the object has not been requested. + + Populated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata */ + deletionTimestamp?: IoK8SApimachineryPkgApisMetaV1Time; + /** Must be empty before the object is deleted from the registry. Each entry is an identifier for the responsible component that will remove the entry from the list. If the deletionTimestamp of the object is non-nil, entries in this list can only be removed. Finalizers may be processed and removed in any order. Order is NOT enforced because it introduces significant risk of stuck finalizers. finalizers is a shared field, any actor with permission can reorder it. If the finalizer list is processed in order, then this can lead to a situation in which the component responsible for the first finalizer in the list is waiting for a signal (field value, external system, or other) produced by a component responsible for a finalizer later in the list, resulting in a deadlock. Without enforced ordering finalizers are free to order amongst themselves and are not vulnerable to ordering changes in the list. */ + finalizers?: string[]; + /** GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. + + If this field is specified and the generated name exists, the server will return a 409. + + Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency */ + generateName?: string; + /** A sequence number representing a specific generation of the desired state. Populated by the system. Read-only. */ + generation?: number; + /** Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels */ + labels?: { + [key: string]: string; + }; + /** ManagedFields maps workflow-id and version to the set of fields that are managed by that workflow. This is mostly for internal housekeeping, and users typically shouldn't need to set or understand this field. A workflow can be the user's name, a controller's name, or the name of a specific apply path like "ci-cd". The set of fields is always in the version that the workflow used when modifying the object. */ + managedFields?: IoK8SApimachineryPkgApisMetaV1ManagedFieldsEntry[]; + /** Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names */ + name?: string; + /** Namespace defines the space within which each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. + + Must be a DNS_LABEL. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces */ + namespace?: string; + /** List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller. */ + ownerReferences?: IoK8SApimachineryPkgApisMetaV1OwnerReference[]; + /** An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. + + Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency */ + resourceVersion?: string; + /** Deprecated: selfLink is a legacy read-only field that is no longer populated by the system. */ + selfLink?: string; + /** UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. + + Populated by the system. Read-only. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids */ + uid?: string; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Integration = { + disableResolveMessage?: boolean; + secureFields?: { + [key: string]: boolean; + }; + settings: { + [key: string]: object; + }; + type: string; + uid?: string; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Spec = { + integrations: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Integration[]; + title: string; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1StatusOperatorState = { + /** descriptiveState is an optional more descriptive state field which has no requirements on format */ + descriptiveState?: string; + /** details contains any extra information that is operator-specific */ + details?: { + [key: string]: object; + }; + /** lastEvaluation is the ResourceVersion last evaluated */ + lastEvaluation: string; + /** state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation. */ + state: string; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Status = { + /** additionalFields is reserved for future use */ + additionalFields?: { + [key: string]: object; + }; + /** operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field. */ + operatorStates?: { + [key: string]: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1StatusOperatorState; + }; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: IoK8SApimachineryPkgApisMetaV1ObjectMeta; + /** Spec is the spec of the Receiver */ + spec: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Spec; + status: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Status; +}; +export type IoK8SApimachineryPkgApisMetaV1ListMeta = { + /** continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message. */ + continue?: string; + /** remainingItemCount is the number of subsequent items in the list which are not included in this list response. If the list request contained label or field selectors, then the number of remaining items is unknown and the field will be left unset and omitted during serialization. If the list is complete (either because it is not chunking or because this is the last chunk), then there are no more remaining items and this field will be left unset and omitted during serialization. Servers older than v1.15 do not set this field. The intended use of the remainingItemCount is *estimating* the size of a collection. Clients should not rely on the remainingItemCount to be set or to be exact. */ + remainingItemCount?: number; + /** String that identifies the server's internal version of this object that can be used by clients to determine when objects have changed. Value must be treated as opaque by clients and passed unmodified back to the server. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency */ + resourceVersion?: string; + /** Deprecated: selfLink is a legacy read-only field that is no longer populated by the system. */ + selfLink?: string; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1ReceiverList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + items: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: IoK8SApimachineryPkgApisMetaV1ListMeta; +}; +export type IoK8SApimachineryPkgApisMetaV1StatusCause = { + /** The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. + + Examples: + "name" - the field "name" on the current resource + "items[0].name" - the field "name" on the first array entry in "items" */ + field?: string; + /** A human-readable description of the cause of the error. This field may be presented as-is to a reader. */ + message?: string; + /** A machine-readable description of the cause of the error. If this value is empty there is no information available. */ + reason?: string; +}; +export type IoK8SApimachineryPkgApisMetaV1StatusDetails = { + /** The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes. */ + causes?: IoK8SApimachineryPkgApisMetaV1StatusCause[]; + /** The group attribute of the resource associated with the status StatusReason. */ + group?: string; + /** The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + /** The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described). */ + name?: string; + /** If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action. */ + retryAfterSeconds?: number; + /** UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids */ + uid?: string; +}; +export type IoK8SApimachineryPkgApisMetaV1Status = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Suggested HTTP return code for this status, 0 if not set. */ + code?: number; + /** Extended data associated with the reason. Each reason may define its own extended details. This field is optional and the data returned is not guaranteed to conform to any schema except that defined by the reason type. */ + details?: IoK8SApimachineryPkgApisMetaV1StatusDetails; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + /** A human-readable description of the status of this operation. */ + message?: string; + /** Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + metadata?: IoK8SApimachineryPkgApisMetaV1ListMeta; + /** A machine-readable description of why this operation is in the "Failure" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it. */ + reason?: string; + /** Status of the operation. One of: "Success" or "Failure". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status */ + status?: string; +}; +export type IoK8SApimachineryPkgApisMetaV1Patch = object; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RouteDefaults = { + group_by?: string[]; + group_interval?: string; + group_wait?: string; + receiver: string; + repeat_interval?: string; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1Matcher = { + label: string; + type: string; + value: string; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1Route = { + continue: boolean; + group_by?: string[]; + group_interval?: string; + group_wait?: string; + matchers?: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1Matcher[]; + mute_time_intervals?: string[]; + receiver?: string; + repeat_interval?: string; + routes?: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1Route[]; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1Spec = { + defaults: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RouteDefaults; + routes: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1Route[]; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1StatusOperatorState = { + /** descriptiveState is an optional more descriptive state field which has no requirements on format */ + descriptiveState?: string; + /** details contains any extra information that is operator-specific */ + details?: { + [key: string]: object; + }; + /** lastEvaluation is the ResourceVersion last evaluated */ + lastEvaluation: string; + /** state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation. */ + state: string; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1Status = { + /** additionalFields is reserved for future use */ + additionalFields?: { + [key: string]: object; + }; + /** operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field. */ + operatorStates?: { + [key: string]: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1StatusOperatorState; + }; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: IoK8SApimachineryPkgApisMetaV1ObjectMeta; + /** Spec is the spec of the RoutingTree */ + spec: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1Spec; + status: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1Status; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTreeList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + items: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: IoK8SApimachineryPkgApisMetaV1ListMeta; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1Spec = { + content: string; + title: string; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1StatusOperatorState = { + /** descriptiveState is an optional more descriptive state field which has no requirements on format */ + descriptiveState?: string; + /** details contains any extra information that is operator-specific */ + details?: { + [key: string]: object; + }; + /** lastEvaluation is the ResourceVersion last evaluated */ + lastEvaluation: string; + /** state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation. */ + state: string; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1Status = { + /** additionalFields is reserved for future use */ + additionalFields?: { + [key: string]: object; + }; + /** operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field. */ + operatorStates?: { + [key: string]: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1StatusOperatorState; + }; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: IoK8SApimachineryPkgApisMetaV1ObjectMeta; + /** Spec is the spec of the TemplateGroup */ + spec: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1Spec; + status: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1Status; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroupList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + items: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: IoK8SApimachineryPkgApisMetaV1ListMeta; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeRange = { + end_time: string; + start_time: string; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1Interval = { + days_of_month?: string[]; + location?: string; + months?: string[]; + times?: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeRange[]; + weekdays?: string[]; + years?: string[]; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1Spec = { + name: string; + time_intervals: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1Interval[]; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1StatusOperatorState = { + /** descriptiveState is an optional more descriptive state field which has no requirements on format */ + descriptiveState?: string; + /** details contains any extra information that is operator-specific */ + details?: { + [key: string]: object; + }; + /** lastEvaluation is the ResourceVersion last evaluated */ + lastEvaluation: string; + /** state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation. */ + state: string; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1Status = { + /** additionalFields is reserved for future use */ + additionalFields?: { + [key: string]: object; + }; + /** operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field. */ + operatorStates?: { + [key: string]: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1StatusOperatorState; + }; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: IoK8SApimachineryPkgApisMetaV1ObjectMeta; + /** Spec is the spec of the TimeInterval */ + spec: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1Spec; + status: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1Status; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeIntervalList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + items: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: IoK8SApimachineryPkgApisMetaV1ListMeta; +}; diff --git a/packages/grafana-alerting/src/grafana/api.ts b/packages/grafana-alerting/src/grafana/api.ts new file mode 100644 index 00000000000..813a3623b2b --- /dev/null +++ b/packages/grafana-alerting/src/grafana/api.ts @@ -0,0 +1,11 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; + +const BASE_URL = '/'; + +export const api = createApi({ + reducerPath: 'grafanaAlertingAPI', + baseQuery: fetchBaseQuery({ + baseUrl: BASE_URL, + }), + endpoints: () => ({}), +}); diff --git a/packages/grafana-alerting/src/grafana/contactPoints/components/ContactPointSelector.tsx b/packages/grafana-alerting/src/grafana/contactPoints/components/ContactPointSelector.tsx new file mode 100644 index 00000000000..02a781719c9 --- /dev/null +++ b/packages/grafana-alerting/src/grafana/contactPoints/components/ContactPointSelector.tsx @@ -0,0 +1,50 @@ +import { chain } from 'lodash'; + +import { Combobox, ComboboxOption } from '@grafana/ui'; + +import { useListContactPoints } from '../hooks/useContactPoints'; +import { ContactPoint } from '../types'; +import { getContactPointDescription } from '../utils'; + +const collator = new Intl.Collator('en', { sensitivity: 'accent' }); + +type ContactPointSelectorProps = { + onChange: (contactPoint: ContactPoint) => void; +}; + +/** + * Contact Point Combobox which lists all available contact points + * @TODO make ComboBox accept a ReactNode so we can use icons and such + */ +function ContactPointSelector({ onChange }: ContactPointSelectorProps) { + const { currentData: contactPoints, isLoading } = useListContactPoints(); + + // Create a mapping of options with their corresponding contact points + const contactPointOptions = chain(contactPoints?.items) + .toArray() + .map((contactPoint) => ({ + option: { + label: contactPoint.spec.title, + value: contactPoint.metadata.uid ?? contactPoint.spec.title, + description: getContactPointDescription(contactPoint), + }, + contactPoint, + })) + .value() + .sort((a, b) => collator.compare(a.option.label, b.option.label)); + + const options = contactPointOptions.map((item) => item.option); + + const handleChange = ({ value }: ComboboxOption) => { + const selectedItem = contactPointOptions.find(({ option }) => option.value === value); + if (!selectedItem) { + return; + } + + onChange(selectedItem.contactPoint); + }; + + return ; +} + +export { ContactPointSelector }; diff --git a/packages/grafana-alerting/src/grafana/contactPoints/hooks/useContactPoints.tsx b/packages/grafana-alerting/src/grafana/contactPoints/hooks/useContactPoints.tsx new file mode 100644 index 00000000000..354c34fe490 --- /dev/null +++ b/packages/grafana-alerting/src/grafana/contactPoints/hooks/useContactPoints.tsx @@ -0,0 +1,29 @@ +import { fetchBaseQuery, TypedUseQueryHookResult } from '@reduxjs/toolkit/query/react'; + +import { config } from '@grafana/runtime'; + +import { alertingAPI, ListReceiverApiArg } from '../../api.gen'; +import { EnhancedListReceiverResponse } from '../types'; + +const { namespace } = config; + +// this is a workaround for the fact that the generated types are not narrow enough +type EnhancedHookResult = TypedUseQueryHookResult< + EnhancedListReceiverResponse, + ListReceiverApiArg, + ReturnType +>; + +/** + * useListContactPoints is a hook that fetches a list of contact points + * + * This function wraps the alertingAPI.useListReceiverQuery with proper typing + * to ensure that the returned ContactPoints are correctly typed in the data.items array. + * + * It automatically uses the configured namespace for the query. + */ +function useListContactPoints() { + return alertingAPI.useListReceiverQuery({ namespace }); +} + +export { useListContactPoints }; diff --git a/packages/grafana-alerting/src/grafana/contactPoints/types.ts b/packages/grafana-alerting/src/grafana/contactPoints/types.ts new file mode 100644 index 00000000000..ade85597d83 --- /dev/null +++ b/packages/grafana-alerting/src/grafana/contactPoints/types.ts @@ -0,0 +1,76 @@ +/** + * This file contains enhanced and type-narrowed versions of the types generated by the RTKQ codegen package. + */ +import { MergeDeep, MergeExclusive, OverrideProperties } from 'type-fest'; + +import { + ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver as ContactPointV0Alpha1, + ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Integration as IntegrationV0Alpha1, + ListReceiverApiResponse, +} from '../api.gen'; + +type GenericIntegration = OverrideProperties< + IntegrationV0Alpha1, + { + settings: Record; + } +>; + +// Based on https://github.com/grafana/alerting/blob/main/receivers/email/config.go#L20-L25 +type EmailIntegration = OverrideProperties< + GenericIntegration, + { + type: 'email'; + settings: { + singleEmail?: boolean; + addresses: string; + message?: string; + subject?: string; + }; + secureFields: never; // email doesn't have any secure fields + } +>; + +// Based on https://github.com/grafana/alerting/blob/main/receivers/slack/config.go +type SlackIntegration = OverrideProperties< + GenericIntegration, + { + type: 'slack'; + settings: { + endpointUrl?: string; + url?: string; + recipient?: string; + text?: string; + title?: string; + username?: string; + icon_emoji?: string; + icon_url?: string; + mentionChannel?: string; + mentionUsers?: string; // comma separated string + mentionGroups?: string; // comma separated string + color?: string; + }; + // secureFields is a union type that can be either a token or a URL but you can't have both + secureFields: MergeExclusive<{ token: string }, { url: string }>; + } +>; + +export type Integration = EmailIntegration | SlackIntegration | GenericIntegration; + +// Enhanced version of ContactPoint with typed integrations +// ⚠️ MergeDeep does not check if the property you are overriding exists in the base type and there is no "DeepOverrideProperties" helper +export type ContactPoint = MergeDeep< + ContactPointV0Alpha1, + { + spec: { + integrations: Integration[]; + }; + } +>; + +export type EnhancedListReceiverResponse = OverrideProperties< + ListReceiverApiResponse, + { + items: ContactPoint[]; + } +>; diff --git a/packages/grafana-alerting/src/grafana/contactPoints/utils.ts b/packages/grafana-alerting/src/grafana/contactPoints/utils.ts new file mode 100644 index 00000000000..871dbb4fd87 --- /dev/null +++ b/packages/grafana-alerting/src/grafana/contactPoints/utils.ts @@ -0,0 +1,31 @@ +import { countBy, isEmpty } from 'lodash'; + +import { ContactPoint } from './types'; + +/** + * Generates a human-readable description of a ContactPoint by summarizing its integrations. + * If the ContactPoint has no integrations, it returns an empty placeholder text. + * + * For integrations, it counts the occurrences of each type and formats them as a comma-separated list. + * Multiple integrations of the same type are indicated with a count in parentheses. + * + * @param contactPoint - The ContactPoint object to describe + * @returns A string description of the ContactPoint's integrations + */ +export function getContactPointDescription(contactPoint: ContactPoint): string { + if (isEmpty(contactPoint.spec.integrations)) { + return ''; + } + + // Count the occurrences of each integration type + const integrationCounts = countBy(contactPoint.spec.integrations, (integration) => integration.type); + + const description = Object.entries(integrationCounts) + .map(([type, count]) => { + // either "email" or "email (2)" but not "email (1)" + return count > 1 ? `${type} (${count})` : type; + }) + .join(', '); + + return description; +} diff --git a/packages/grafana-alerting/src/index.ts b/packages/grafana-alerting/src/index.ts new file mode 100644 index 00000000000..ee4141e38d0 --- /dev/null +++ b/packages/grafana-alerting/src/index.ts @@ -0,0 +1,9 @@ +/** + * Export things here that you want to be available under @grafana/alerting + * + * ⚠️ This implies everything in here is public API and should be considered stable and treated as such – make sure to + * think carefully about what you export here and the interfaces / data structures. + * + * Breaking changes should be avoided to maintain backwards compatibility for consumers of this package. + */ +export default {}; diff --git a/packages/grafana-alerting/src/internal.ts b/packages/grafana-alerting/src/internal.ts new file mode 100644 index 00000000000..800a6a5ee91 --- /dev/null +++ b/packages/grafana-alerting/src/internal.ts @@ -0,0 +1,6 @@ +/** + * Export things here that you want to be available under @grafana/alerting/internal + */ +export { alertingAPI } from './grafana/api.gen'; + +export default {}; diff --git a/packages/grafana-alerting/src/unstable.ts b/packages/grafana-alerting/src/unstable.ts new file mode 100644 index 00000000000..005c320eaa8 --- /dev/null +++ b/packages/grafana-alerting/src/unstable.ts @@ -0,0 +1,8 @@ +/** + * Export things here that you want to be available under @grafana/alerting/unstable + */ + +// Contact Points +export * from './grafana/contactPoints/types'; +export { useListContactPoints } from './grafana/contactPoints/hooks/useContactPoints'; +export { ContactPointSelector } from './grafana/contactPoints/components/ContactPointSelector'; diff --git a/packages/grafana-alerting/tsconfig.build.json b/packages/grafana-alerting/tsconfig.build.json new file mode 100644 index 00000000000..54309163ebc --- /dev/null +++ b/packages/grafana-alerting/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "exclude": ["dist", "node_modules", "test", "**/*.test.ts*"], + "extends": "./tsconfig.json" +} diff --git a/packages/grafana-alerting/tsconfig.json b/packages/grafana-alerting/tsconfig.json new file mode 100644 index 00000000000..2ec42c374d6 --- /dev/null +++ b/packages/grafana-alerting/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "declarationDir": "./compiled", + "emitDeclarationOnly": true, + "isolatedModules": true, + "rootDirs": ["."] + }, + "exclude": ["dist/**/*"], + "extends": "@grafana/tsconfig", + "include": ["typings/jest", "../../public/app/types/*.d.ts", "../grafana-ui/src/types/*.d.ts", "src/**/*.ts*"] +} diff --git a/yarn.lock b/yarn.lock index cdfd94dfddf..74f9140805c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -99,7 +99,7 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:7.26.10, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.9, @babel/core@npm:^7.22.9": +"@babel/core@npm:7.26.10": version: 7.26.10 resolution: "@babel/core@npm:7.26.10" dependencies: @@ -122,7 +122,43 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.22.9, @babel/generator@npm:^7.26.10, @babel/generator@npm:^7.27.0, @babel/generator@npm:^7.7.2": +"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.9, @babel/core@npm:^7.22.9": + version: 7.26.9 + resolution: "@babel/core@npm:7.26.9" + dependencies: + "@ampproject/remapping": "npm:^2.2.0" + "@babel/code-frame": "npm:^7.26.2" + "@babel/generator": "npm:^7.26.9" + "@babel/helper-compilation-targets": "npm:^7.26.5" + "@babel/helper-module-transforms": "npm:^7.26.0" + "@babel/helpers": "npm:^7.26.9" + "@babel/parser": "npm:^7.26.9" + "@babel/template": "npm:^7.26.9" + "@babel/traverse": "npm:^7.26.9" + "@babel/types": "npm:^7.26.9" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10/ceed199dbe25f286a0a59a2ea7879aed37c1f3bb289375d061eda4752cab2ba365e7f9e969c7fd3b9b95c930493db6eeb5a6d6f017dd135fb5a4503449aad753 + languageName: node + linkType: hard + +"@babel/generator@npm:^7.22.9, @babel/generator@npm:^7.26.9, @babel/generator@npm:^7.7.2": + version: 7.26.9 + resolution: "@babel/generator@npm:7.26.9" + dependencies: + "@babel/parser": "npm:^7.26.9" + "@babel/types": "npm:^7.26.9" + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.25" + jsesc: "npm:^3.0.2" + checksum: 10/95075dd6158a49efcc71d7f2c5d20194fcf245348de7723ca35e37cd5800587f1d4de2be6c4ba87b5f5fbb967c052543c109eaab14b43f6a73eb05ccd9a5bb44 + languageName: node + linkType: hard + +"@babel/generator@npm:^7.26.10, @babel/generator@npm:^7.27.0": version: 7.27.0 resolution: "@babel/generator@npm:7.27.0" dependencies: @@ -339,6 +375,16 @@ __metadata: languageName: node linkType: hard +"@babel/helpers@npm:^7.26.9": + version: 7.26.9 + resolution: "@babel/helpers@npm:7.26.9" + dependencies: + "@babel/template": "npm:^7.26.9" + "@babel/types": "npm:^7.26.9" + checksum: 10/267dfa7d04dff7720610497f466aa7b60652b7ec8dde5914527879350c9d655271e892117c5b2f0f083d92d2a8e5e2cf9832d4f98cd7fb72d78f796002af19a1 + languageName: node + linkType: hard + "@babel/highlight@npm:^7.25.7": version: 7.25.9 resolution: "@babel/highlight@npm:7.25.9" @@ -351,7 +397,18 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.26.10, @babel/parser@npm:^7.27.0": +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.26.9": + version: 7.26.9 + resolution: "@babel/parser@npm:7.26.9" + dependencies: + "@babel/types": "npm:^7.26.9" + bin: + parser: ./bin/babel-parser.js + checksum: 10/cb84fe3ba556d6a4360f3373cf7eb0901c46608c8d77330cc1ca021d60f5d6ebb4056a8e7f9dd0ef231923ef1fe69c87b11ce9e160d2252e089a20232a2b942b + languageName: node + linkType: hard + +"@babel/parser@npm:^7.26.10, @babel/parser@npm:^7.27.0": version: 7.27.0 resolution: "@babel/parser@npm:7.27.0" dependencies: @@ -1416,7 +1473,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:7.27.0, @babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.5, @babel/runtime@npm:^7.24.7, @babel/runtime@npm:^7.25.0, @babel/runtime@npm:^7.25.6, @babel/runtime@npm:^7.25.7, @babel/runtime@npm:^7.26.10, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7": +"@babel/runtime@npm:7.27.0, @babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.5, @babel/runtime@npm:^7.24.7, @babel/runtime@npm:^7.25.0, @babel/runtime@npm:^7.25.6, @babel/runtime@npm:^7.25.7, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7": version: 7.27.0 resolution: "@babel/runtime@npm:7.27.0" dependencies: @@ -1425,7 +1482,18 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.22.5, @babel/template@npm:^7.24.7, @babel/template@npm:^7.25.9, @babel/template@npm:^7.26.9, @babel/template@npm:^7.27.0, @babel/template@npm:^7.3.3": +"@babel/template@npm:^7.22.5, @babel/template@npm:^7.24.7, @babel/template@npm:^7.25.9, @babel/template@npm:^7.26.9, @babel/template@npm:^7.3.3": + version: 7.26.9 + resolution: "@babel/template@npm:7.26.9" + dependencies: + "@babel/code-frame": "npm:^7.26.2" + "@babel/parser": "npm:^7.26.9" + "@babel/types": "npm:^7.26.9" + checksum: 10/240288cebac95b1cc1cb045ad143365643da0470e905e11731e63280e43480785bd259924f4aea83898ef68e9fa7c176f5f2d1e8b0a059b27966e8ca0b41a1b6 + languageName: node + linkType: hard + +"@babel/template@npm:^7.27.0": version: 7.27.0 resolution: "@babel/template@npm:7.27.0" dependencies: @@ -1436,7 +1504,22 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.26.10, @babel/traverse@npm:^7.26.8": +"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.26.8, @babel/traverse@npm:^7.26.9": + version: 7.26.9 + resolution: "@babel/traverse@npm:7.26.9" + dependencies: + "@babel/code-frame": "npm:^7.26.2" + "@babel/generator": "npm:^7.26.9" + "@babel/parser": "npm:^7.26.9" + "@babel/template": "npm:^7.26.9" + "@babel/types": "npm:^7.26.9" + debug: "npm:^4.3.1" + globals: "npm:^11.1.0" + checksum: 10/c16a79522eafa0a7e40eb556bf1e8a3d50dbb0ff943a80f2c06cee2ec7ff87baa0c5d040a5cff574d9bcb3bed05e7d8c6f13b238a931c97267674b02c6cf45b4 + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.26.10": version: 7.27.0 resolution: "@babel/traverse@npm:7.27.0" dependencies: @@ -1461,6 +1544,16 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.26.9": + version: 7.26.9 + resolution: "@babel/types@npm:7.26.9" + dependencies: + "@babel/helper-string-parser": "npm:^7.25.9" + "@babel/helper-validator-identifier": "npm:^7.25.9" + checksum: 10/11b62ea7ed64ef7e39cc9b33852c1084064c3b970ae0eaa5db659241cfb776577d1e68cbff4de438bada885d3a827b52cc0f3746112d8e1bc672bb99a8eb5b56 + languageName: node + linkType: hard + "@bcoe/v8-coverage@npm:^0.2.3": version: 0.2.3 resolution: "@bcoe/v8-coverage@npm:0.2.3" @@ -2376,15 +2469,15 @@ __metadata: languageName: node linkType: hard -"@formatjs/ecma402-abstract@npm:2.3.4": - version: 2.3.4 - resolution: "@formatjs/ecma402-abstract@npm:2.3.4" +"@formatjs/ecma402-abstract@npm:2.3.2": + version: 2.3.2 + resolution: "@formatjs/ecma402-abstract@npm:2.3.2" dependencies: - "@formatjs/fast-memoize": "npm:2.2.7" - "@formatjs/intl-localematcher": "npm:0.6.1" - decimal.js: "npm:^10.4.3" - tslib: "npm:^2.8.0" - checksum: 10/573971ffc291096a4b9fcc80b4708124e89bf2e3ac50e0f78b41eb797e9aa1b842f4dc3665e4467a853c738386821769d9e40408a1d25bc73323a1f057a16cf2 + "@formatjs/fast-memoize": "npm:2.2.6" + "@formatjs/intl-localematcher": "npm:0.5.10" + decimal.js: "npm:10" + tslib: "npm:2" + checksum: 10/db31d3d9b36033ea11ec905638ac0c1d2282f5bf53c9c06ee1d0ffd924f4bf64030702c92b56261756c4998dfa6235462689d8eda82d5913f2d7cf636a9416ae languageName: node linkType: hard @@ -2397,12 +2490,12 @@ __metadata: languageName: node linkType: hard -"@formatjs/fast-memoize@npm:2.2.7": - version: 2.2.7 - resolution: "@formatjs/fast-memoize@npm:2.2.7" +"@formatjs/fast-memoize@npm:2.2.6": + version: 2.2.6 + resolution: "@formatjs/fast-memoize@npm:2.2.6" dependencies: - tslib: "npm:^2.8.0" - checksum: 10/e7e6efc677d63a13d99a854305db471b69f64cbfebdcb6dbe507dab9aa7eaae482ca5de86f343c856ca0a2c8f251672bd1f37c572ce14af602c0287378097d43 + tslib: "npm:2" + checksum: 10/efa5601dddbd94412ee567d5d067dfd206afa2d08553435f6938e69acba3309b83b9b15021cd30550d5fb93817a53b7691098a11a73f621c2d9318efad49fd76 languageName: node linkType: hard @@ -2428,13 +2521,13 @@ __metadata: linkType: hard "@formatjs/intl-durationformat@npm:^0.7.0": - version: 0.7.4 - resolution: "@formatjs/intl-durationformat@npm:0.7.4" + version: 0.7.2 + resolution: "@formatjs/intl-durationformat@npm:0.7.2" dependencies: - "@formatjs/ecma402-abstract": "npm:2.3.4" - "@formatjs/intl-localematcher": "npm:0.6.1" - tslib: "npm:^2.8.0" - checksum: 10/d62273ecd635475ca91e9b501301f3f396403fa91b584c550734b19b2d194ba1316b27303fed985c1d42ae933d54eb220da6540edfdf376b0d9371ecfd0d4e15 + "@formatjs/ecma402-abstract": "npm:2.3.2" + "@formatjs/intl-localematcher": "npm:0.5.10" + tslib: "npm:2" + checksum: 10/863784f3ac51779516fa29c87705af6d4f1b32191a2a48172d2ce16a99e385887a5641703a222ca2b1a0d334e1ab2587be5ef65f48c5ae8a16ba55138356a232 languageName: node linkType: hard @@ -2447,12 +2540,12 @@ __metadata: languageName: node linkType: hard -"@formatjs/intl-localematcher@npm:0.6.1": - version: 0.6.1 - resolution: "@formatjs/intl-localematcher@npm:0.6.1" +"@formatjs/intl-localematcher@npm:0.5.10": + version: 0.5.10 + resolution: "@formatjs/intl-localematcher@npm:0.5.10" dependencies: - tslib: "npm:^2.8.0" - checksum: 10/c7b3bc8395d18670677f207b2fd107561fff5d6394a9b4273c29e0bea920300ec3a2eefead600ebb7761c04a770cada28f78ac059f84d00520bfb57a9db36998 + tslib: "npm:2" + checksum: 10/119e36974607d5d3586570db93358c1f316c0736b83acc81dce88468435a9519a9e58a5abe6ed3078ffe40d6b4ad4613c6371e2a39cd570c9b6f561372ffb14d languageName: node linkType: hard @@ -2911,6 +3004,29 @@ __metadata: languageName: unknown linkType: soft +"@grafana/alerting@workspace:*, @grafana/alerting@workspace:packages/grafana-alerting": + version: 0.0.0-use.local + resolution: "@grafana/alerting@workspace:packages/grafana-alerting" + dependencies: + "@grafana/tsconfig": "npm:^2.0.0" + "@reduxjs/toolkit": "npm:^2.7.0" + "@rtk-query/codegen-openapi": "npm:^2.0.0" + "@types/lodash": "npm:^4" + "@types/react": "npm:18.3.18" + "@types/react-dom": "npm:18.3.5" + lodash: "npm:^4.17.21" + react: "npm:18.3.1" + react-dom: "npm:18.3.1" + type-fest: "npm:^4.40.0" + typescript: "npm:5.7.3" + peerDependencies: + "@grafana/runtime": ^12.0.0-pre + "@grafana/ui": ^12.0.0-pre + react: ^18.0.0 + react-dom: ^18.0.0 + languageName: unknown + linkType: soft + "@grafana/async-query-data@npm:0.3.0": version: 0.3.0 resolution: "@grafana/async-query-data@npm:0.3.0" @@ -6504,6 +6620,28 @@ __metadata: languageName: node linkType: hard +"@reduxjs/toolkit@npm:^2.7.0": + version: 2.7.0 + resolution: "@reduxjs/toolkit@npm:2.7.0" + dependencies: + "@standard-schema/spec": "npm:^1.0.0" + "@standard-schema/utils": "npm:^0.3.0" + immer: "npm:^10.0.3" + redux: "npm:^5.0.1" + redux-thunk: "npm:^3.1.0" + reselect: "npm:^5.1.0" + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + checksum: 10/cc264efc95f9ebeafa469bf1040d106a33768a802e6f46aa678bf9f26822d049c18b5f10864aa8badb2e62febe58e242860256174528e62b09e8f897d32cd182 + languageName: node + linkType: hard + "@remix-run/router@npm:1.19.1": version: 1.19.1 resolution: "@remix-run/router@npm:1.19.1" @@ -7023,6 +7161,20 @@ __metadata: languageName: node linkType: hard +"@standard-schema/spec@npm:^1.0.0": + version: 1.0.0 + resolution: "@standard-schema/spec@npm:1.0.0" + checksum: 10/aee780cc1431888ca4b9aba9b24ffc8f3073fc083acc105e3951481478a2f4dc957796931b2da9e2d8329584cf211e4542275f188296c1cdff3ed44fd93a8bc8 + languageName: node + linkType: hard + +"@standard-schema/utils@npm:^0.3.0": + version: 0.3.0 + resolution: "@standard-schema/utils@npm:0.3.0" + checksum: 10/7084f875d322792f2e0a5904009434c8374b9345b09ba89828b68fd56fa3c2b366d35bf340d9e8c72736ef01793c2f70d350c372ed79845dc3566c58d34b4b51 + languageName: node + linkType: hard + "@storybook/addon-a11y@npm:^8.6.2": version: 8.6.2 resolution: "@storybook/addon-a11y@npm:8.6.2" @@ -7560,6 +7712,20 @@ __metadata: languageName: node linkType: hard +"@swagger-api/apidom-ast@npm:^1.0.0-beta.5": + version: 1.0.0-beta.12 + resolution: "@swagger-api/apidom-ast@npm:1.0.0-beta.12" + dependencies: + "@babel/runtime-corejs3": "npm:^7.20.7" + "@swagger-api/apidom-error": "npm:^1.0.0-beta.12" + "@types/ramda": "npm:~0.30.0" + ramda: "npm:~0.30.0" + ramda-adjunct: "npm:^5.0.0" + unraw: "npm:^3.0.0" + checksum: 10/adbcdb40d343eb2b18804ba82e3b0059e66fbb9df5fc43d8a968e6b8b80b68356180a430fbf237892b47a40f7713c919023dc4cd9de92b2ae7ff1c717775d30f + languageName: node + linkType: hard + "@swagger-api/apidom-core@npm:>=1.0.0-beta.13 <1.0.0-rc.0, @swagger-api/apidom-core@npm:^1.0.0-beta.30, @swagger-api/apidom-core@npm:^1.0.0-beta.5": version: 1.0.0-beta.30 resolution: "@swagger-api/apidom-core@npm:1.0.0-beta.30" @@ -7586,6 +7752,15 @@ __metadata: languageName: node linkType: hard +"@swagger-api/apidom-error@npm:^1.0.0-beta.12": + version: 1.0.0-beta.12 + resolution: "@swagger-api/apidom-error@npm:1.0.0-beta.12" + dependencies: + "@babel/runtime-corejs3": "npm:^7.20.7" + checksum: 10/644ad779bfb464b3cf1588f2ca230910782e61ca0478a5ec1a40b98a5715d976e59830d2867f3ffe1081e7c820c88d5d72dfce1ea4d3d2d8dcf2f8557b64feab + languageName: node + linkType: hard + "@swagger-api/apidom-json-pointer@npm:>=1.0.0-beta.13 <1.0.0-rc.0, @swagger-api/apidom-json-pointer@npm:^1.0.0-beta.3 <1.0.0-rc.0, @swagger-api/apidom-json-pointer@npm:^1.0.0-beta.30": version: 1.0.0-beta.30 resolution: "@swagger-api/apidom-json-pointer@npm:1.0.0-beta.30" @@ -7865,7 +8040,26 @@ __metadata: languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-json@npm:^1.0.0-beta.3 <1.0.0-rc.0, @swagger-api/apidom-parser-adapter-json@npm:^1.0.0-beta.30, @swagger-api/apidom-parser-adapter-json@npm:^1.0.0-beta.5": +"@swagger-api/apidom-parser-adapter-json@npm:^1.0.0-beta.3 <1.0.0-rc.0, @swagger-api/apidom-parser-adapter-json@npm:^1.0.0-beta.5": + version: 1.0.0-beta.5 + resolution: "@swagger-api/apidom-parser-adapter-json@npm:1.0.0-beta.5" + dependencies: + "@babel/runtime-corejs3": "npm:^7.20.7" + "@swagger-api/apidom-ast": "npm:^1.0.0-beta.5" + "@swagger-api/apidom-core": "npm:^1.0.0-beta.5" + "@swagger-api/apidom-error": "npm:^1.0.0-beta.5" + "@types/ramda": "npm:~0.30.0" + node-gyp: "npm:latest" + ramda: "npm:~0.30.0" + ramda-adjunct: "npm:^5.0.0" + tree-sitter: "npm:=0.22.1" + tree-sitter-json: "npm:=0.24.8" + web-tree-sitter: "npm:=0.24.5" + checksum: 10/dc28419cbc068f5e2ecc58b0ccf807bb8717625c6043d7661d1488c054617384154d9990d6e5abed52f60209806f5b2a9eb5e20f3cd6d839290e69f6acc5871b + languageName: node + linkType: hard + +"@swagger-api/apidom-parser-adapter-json@npm:^1.0.0-beta.30": version: 1.0.0-beta.30 resolution: "@swagger-api/apidom-parser-adapter-json@npm:1.0.0-beta.30" dependencies: @@ -7974,7 +8168,26 @@ __metadata: languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-yaml-1-2@npm:^1.0.0-beta.3 <1.0.0-rc.0, @swagger-api/apidom-parser-adapter-yaml-1-2@npm:^1.0.0-beta.30, @swagger-api/apidom-parser-adapter-yaml-1-2@npm:^1.0.0-beta.5": +"@swagger-api/apidom-parser-adapter-yaml-1-2@npm:^1.0.0-beta.3 <1.0.0-rc.0, @swagger-api/apidom-parser-adapter-yaml-1-2@npm:^1.0.0-beta.5": + version: 1.0.0-beta.5 + resolution: "@swagger-api/apidom-parser-adapter-yaml-1-2@npm:1.0.0-beta.5" + dependencies: + "@babel/runtime-corejs3": "npm:^7.20.7" + "@swagger-api/apidom-ast": "npm:^1.0.0-beta.5" + "@swagger-api/apidom-core": "npm:^1.0.0-beta.5" + "@swagger-api/apidom-error": "npm:^1.0.0-beta.5" + "@tree-sitter-grammars/tree-sitter-yaml": "npm:=0.7.0" + "@types/ramda": "npm:~0.30.0" + node-gyp: "npm:latest" + ramda: "npm:~0.30.0" + ramda-adjunct: "npm:^5.0.0" + tree-sitter: "npm:=0.22.1" + web-tree-sitter: "npm:=0.24.5" + checksum: 10/8f14014d18a674447aa1e2fac4251794538f05cd86c79debcbd7cbfa7efaa3403385d292da6f96988b3d9f60b07b81e6cfce7a1bc779240513d6526160f7c116 + languageName: node + linkType: hard + +"@swagger-api/apidom-parser-adapter-yaml-1-2@npm:^1.0.0-beta.30": version: 1.0.0-beta.30 resolution: "@swagger-api/apidom-parser-adapter-yaml-1-2@npm:1.0.0-beta.30" dependencies: @@ -9394,6 +9607,13 @@ __metadata: languageName: node linkType: hard +"@types/lodash@npm:^4": + version: 4.17.16 + resolution: "@types/lodash@npm:4.17.16" + checksum: 10/9a8bb7471a7521bd65d528e1bd14f79819a3eeb6f8a35a8a44649a7d773775c0813e93fd93bd32ccf350bb076c0bf02c6d47877c4625f526f6dd4d283c746aec + languageName: node + linkType: hard + "@types/logfmt@npm:^1.2.3": version: 1.2.6 resolution: "@types/logfmt@npm:1.2.6" @@ -9898,9 +10118,9 @@ __metadata: linkType: hard "@types/symlink-or-copy@npm:^1.2.0": - version: 1.2.2 - resolution: "@types/symlink-or-copy@npm:1.2.2" - checksum: 10/fb8fc2a356d1d7ab222bbea772ca6bf1b4a90b1f14ea46c1522643d3afd018f3b938ead7ba04af88175432a07497c02904bf428008505acf8a4745f1bc6e3892 + version: 1.2.0 + resolution: "@types/symlink-or-copy@npm:1.2.0" + checksum: 10/554d9255387a9da98b685fe74b70d8b123063cf78b8f3be0de2c768cb0f37515d1edb0334be908585d77c6409e79202faf1a66ee8e78bb06ca168a2028d0c3f3 languageName: node linkType: hard @@ -11445,13 +11665,6 @@ __metadata: languageName: node linkType: hard -"b4a@npm:^1.6.4": - version: 1.6.7 - resolution: "b4a@npm:1.6.7" - checksum: 10/1ac056e3bce378d4d3e570e57319360a9d3125ab6916a1921b95bea33d9ee646698ebc75467561fd6fcc80ff697612124c89bb9b95e80db94c6dc23fcb977705 - languageName: node - linkType: hard - "babel-jest@npm:29.7.0, babel-jest@npm:^29.7.0": version: 29.7.0 resolution: "babel-jest@npm:29.7.0" @@ -11614,13 +11827,6 @@ __metadata: languageName: node linkType: hard -"bare-events@npm:^2.2.0": - version: 2.5.4 - resolution: "bare-events@npm:2.5.4" - checksum: 10/135ef380b13f554ca2c6905bdbcfac8edae08fce85b7f953fa01f09a9f5b0da6a25e414111659bc9a6118216f0dd1f732016acd11ce91517f2afb26ebeb4b721 - languageName: node - linkType: hard - "baron@npm:3.0.3": version: 3.0.3 resolution: "baron@npm:3.0.3" @@ -14292,7 +14498,7 @@ __metadata: languageName: node linkType: hard -"decimal.js@npm:^10.4.1, decimal.js@npm:^10.4.3": +"decimal.js@npm:10, decimal.js@npm:^10.4.1": version: 10.5.0 resolution: "decimal.js@npm:10.5.0" checksum: 10/714d49cf2f2207b268221795ede330e51452b7c451a0c02a770837d2d4faed47d603a729c2aa1d952eb6c4102d999e91c9b952c1aa016db3c5cba9fc8bf4cda2 @@ -14737,13 +14943,13 @@ __metadata: linkType: hard "domutils@npm:^3.0.1, domutils@npm:^3.1.0": - version: 3.2.2 - resolution: "domutils@npm:3.2.2" + version: 3.1.0 + resolution: "domutils@npm:3.1.0" dependencies: dom-serializer: "npm:^2.0.0" domelementtype: "npm:^2.3.0" domhandler: "npm:^5.0.3" - checksum: 10/2e08842151aa406f50fe5e6d494f4ec73c2373199fa00d1f77b56ec604e566b7f226312ae35ab8160bb7f27a27c7285d574c8044779053e499282ca9198be210 + checksum: 10/9a169a6e57ac4c738269a73ab4caf785114ed70e46254139c1bbc8144ac3102aacb28a6149508395ae34aa5d6a40081f4fa5313855dc8319c6d8359866b6dfea languageName: node linkType: hard @@ -16145,7 +16351,7 @@ __metadata: languageName: node linkType: hard -"fast-fifo@npm:^1.3.2": +"fast-fifo@npm:^1.1.0": version: 1.3.2 resolution: "fast-fifo@npm:1.3.2" checksum: 10/6bfcba3e4df5af7be3332703b69a7898a8ed7020837ec4395bb341bd96cc3a6d86c3f6071dd98da289618cf2234c70d84b2a6f09a33dd6f988b1ff60d8e54275 @@ -16236,11 +16442,11 @@ __metadata: linkType: hard "fastq@npm:^1.13.0, fastq@npm:^1.6.0": - version: 1.19.1 - resolution: "fastq@npm:1.19.1" + version: 1.17.1 + resolution: "fastq@npm:1.17.1" dependencies: reusify: "npm:^1.0.4" - checksum: 10/75679dc226316341c4f2a6b618571f51eac96779906faecd8921b984e844d6ae42fabb2df69b1071327d398d5716693ea9c9c8941f64ac9e89ec2032ce59d730 + checksum: 10/a443180068b527dd7b3a63dc7f2a47ceca2f3e97b9c00a1efe5538757e6cc4056a3526df94308075d7727561baf09ebaa5b67da8dcbddb913a021c5ae69d1f69 languageName: node linkType: hard @@ -17357,6 +17563,7 @@ __metadata: "@floating-ui/react": "npm:0.27.7" "@formatjs/intl-durationformat": "npm:^0.7.0" "@glideapps/glide-data-grid": "npm:^6.0.0" + "@grafana/alerting": "workspace:*" "@grafana/aws-sdk": "npm:0.6.0" "@grafana/azure-sdk": "npm:0.0.7" "@grafana/data": "workspace:*" @@ -18433,16 +18640,16 @@ __metadata: linkType: hard "i18next@npm:^23.5.1 || ^24.2.0, i18next@npm:^24.0.0": - version: 24.2.3 - resolution: "i18next@npm:24.2.3" + version: 24.2.2 + resolution: "i18next@npm:24.2.2" dependencies: - "@babel/runtime": "npm:^7.26.10" + "@babel/runtime": "npm:^7.23.2" peerDependencies: typescript: ^5 peerDependenciesMeta: typescript: optional: true - checksum: 10/6c73d964f2a98b1aa2c2717fe6da66fc265bcbbc5fcd52b2bfff51ff013d30d4f7d7449c4eb7f464d27af43e2e73f2e7f1d46a144d731bd3bdb1385d4c199e4c + checksum: 10/f66ed9e56d9412e59502f5df39163631daf9f1264774732fb21edbd66a528ca7a6b67dc2e2aec95683c6c7956e42c651587a54bd8ee082bd12008880ce6cd326 languageName: node linkType: hard @@ -23508,12 +23715,12 @@ __metadata: linkType: hard "parse5-htmlparser2-tree-adapter@npm:^7.0.0": - version: 7.1.0 - resolution: "parse5-htmlparser2-tree-adapter@npm:7.1.0" + version: 7.0.0 + resolution: "parse5-htmlparser2-tree-adapter@npm:7.0.0" dependencies: - domhandler: "npm:^5.0.3" + domhandler: "npm:^5.0.2" parse5: "npm:^7.0.0" - checksum: 10/75910af9137451e9c53e1e0d712f7393f484e89e592b1809ee62ad6cedd61b98daeaa5206ff5d9f06778002c91fac311afedde4880e1916fdb44fa71199dae73 + checksum: 10/23dbe45fdd338fe726cf5c55b236e1f403aeb0c1b926e18ab8ef0aa580980a25f8492d160fe2ed0ec906c3c8e38b51e68ef5620a3b9460d9458ea78946a3f7c0 languageName: node linkType: hard @@ -24708,6 +24915,13 @@ __metadata: languageName: node linkType: hard +"queue-tick@npm:^1.0.1": + version: 1.0.1 + resolution: "queue-tick@npm:1.0.1" + checksum: 10/f447926c513b64a857906f017a3b350f7d11277e3c8d2a21a42b7998fa1a613d7a829091e12d142bb668905c8f68d8103416c7197856efb0c72fa835b8e254b5 + languageName: node + linkType: hard + "queue-typescript@npm:^1.0.1": version: 1.0.1 resolution: "queue-typescript@npm:1.0.1" @@ -26606,13 +26820,20 @@ __metadata: languageName: node linkType: hard -"resolve.exports@npm:2.0.3, resolve.exports@npm:^2.0.0": +"resolve.exports@npm:2.0.3": version: 2.0.3 resolution: "resolve.exports@npm:2.0.3" checksum: 10/536efee0f30a10fac8604e6cdc7844dbc3f4313568d09f06db4f7ed8a5b8aeb8585966fe975083d1f2dfbc87cf5f8bc7ab65a5c23385c14acbb535ca79f8398a languageName: node linkType: hard +"resolve.exports@npm:^2.0.0": + version: 2.0.2 + resolution: "resolve.exports@npm:2.0.2" + checksum: 10/f1cc0b6680f9a7e0345d783e0547f2a5110d8336b3c2a4227231dd007271ffd331fd722df934f017af90bae0373920ca0d4005da6f76cb3176c8ae426370f893 + languageName: node + linkType: hard + "resolve@npm:^1.10.0, resolve@npm:^1.14.2, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.4, resolve@npm:^1.22.8": version: 1.22.8 resolution: "resolve@npm:1.22.8" @@ -27922,11 +28143,11 @@ __metadata: linkType: hard "sort-keys@npm:^5.0.0": - version: 5.1.0 - resolution: "sort-keys@npm:5.1.0" + version: 5.0.0 + resolution: "sort-keys@npm:5.0.0" dependencies: is-plain-obj: "npm:^4.0.0" - checksum: 10/d14936082b2fd1efbddb42c1f7ece39acf8c2e54e4bc65b92ee634ffc7a4a955bdfb334f28ce1273c947611c11f8a73711d147dc43922172a782eb4d71b8c3a2 + checksum: 10/9c0b7a468312075be03770b260b2cc0e5d55149025e564edaed41c9ff619199698aad6712a6fe4bbc75c541efb081276ac6bbd4cf2723d742f272f7a8fe354f5 languageName: node linkType: hard @@ -28315,16 +28536,12 @@ __metadata: linkType: hard "streamx@npm:^2.12.0, streamx@npm:^2.12.5, streamx@npm:^2.13.2, streamx@npm:^2.14.0": - version: 2.22.0 - resolution: "streamx@npm:2.22.0" + version: 2.15.7 + resolution: "streamx@npm:2.15.7" dependencies: - bare-events: "npm:^2.2.0" - fast-fifo: "npm:^1.3.2" - text-decoder: "npm:^1.1.0" - dependenciesMeta: - bare-events: - optional: true - checksum: 10/9c329bb316e2085e207e471ecd0da18b4ed5b1cfe5cf10e9e7fad3f8f50c6ca1a6a844bdfd9bc7521560b97f229890de82ca162a0e66115300b91a489b1cbefd + fast-fifo: "npm:^1.1.0" + queue-tick: "npm:^1.0.1" + checksum: 10/5f59f49383b41546e87b15ba54970cca262d1eb0826c5b88c4dc5329d18821fed06f92f849da68c764ad91ea05f394518510957371cb0abd3617bdabed465cdb languageName: node linkType: hard @@ -29068,15 +29285,6 @@ __metadata: languageName: node linkType: hard -"text-decoder@npm:^1.1.0": - version: 1.2.3 - resolution: "text-decoder@npm:1.2.3" - dependencies: - b4a: "npm:^1.6.4" - checksum: 10/bcdec33c0f070aeac38e46e4cafdcd567a58473ed308bdf75260bfbd8f7dc76acbc0b13226afaec4a169d0cb44cec2ab89c57b6395ccf02e941eaebbe19e124a - languageName: node - linkType: hard - "text-extensions@npm:^1.0.0": version: 1.9.0 resolution: "text-extensions@npm:1.9.0" @@ -29529,6 +29737,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:2, tslib@npm:2.8.1, tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.4.1, tslib@npm:^2.6.2, tslib@npm:^2.7.0, tslib@npm:^2.8.0": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7 + languageName: node + linkType: hard + "tslib@npm:2.4.0": version: 2.4.0 resolution: "tslib@npm:2.4.0" @@ -29543,13 +29758,6 @@ __metadata: languageName: node linkType: hard -"tslib@npm:2.8.1, tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.4.1, tslib@npm:^2.6.2, tslib@npm:^2.7.0, tslib@npm:^2.8.0": - version: 2.8.1 - resolution: "tslib@npm:2.8.1" - checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7 - languageName: node - linkType: hard - "tslib@npm:^1.10.0, tslib@npm:^1.8.1": version: 1.14.1 resolution: "tslib@npm:1.14.1" @@ -29681,6 +29889,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^4.40.0": + version: 4.40.0 + resolution: "type-fest@npm:4.40.0" + checksum: 10/dbca20979d18c6b8c87ca28cd999d9ae6b34e0c54c3a87ac65530a32f7a178d38d3788044a589f47c9fde3f3c81422e7b021ec1455f7242b724a2d9c642ce8b8 + languageName: node + linkType: hard + "type-is@npm:~1.6.18": version: 1.6.18 resolution: "type-is@npm:1.6.18" @@ -29887,9 +30102,9 @@ __metadata: linkType: hard "undici@npm:^6.19.5": - version: 6.21.2 - resolution: "undici@npm:6.21.2" - checksum: 10/9cd9ead22599c23aa2a7dfa5b80fa1491bebb294bf1dc64c9c0f90ea4ec8e272a4db2810e2565d65b952249f105523d2929d7cc951f88cf0a1f082143bae8d75 + version: 6.19.8 + resolution: "undici@npm:6.19.8" + checksum: 10/19ae4ba38b029a664d99fd330935ef59136cf99edb04ed821042f27b5a9e84777265fb744c8a7abc83f2059afb019446c69a4ebef07bbc0ed6b2de8d67ef4090 languageName: node linkType: hard From 8e6b8fad01c03f43b63c219fe5a0cabd80a45c1f Mon Sep 17 00:00:00 2001 From: Oscar Kilhed Date: Thu, 24 Apr 2025 14:13:18 +0200 Subject: [PATCH 086/146] SchemaV2: Fix saving of transparent setting of vizPanel (#104443) * save transparent setting * make sure we test both transparent and non transparent --- .../__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap | 1 + .../serialization/transformSceneToSaveModelSchemaV2.test.ts | 1 - .../serialization/transformSceneToSaveModelSchemaV2.ts | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap index db3d9c0644a..c630c36d2e6 100644 --- a/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap +++ b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap @@ -71,6 +71,7 @@ exports[`transformSceneToSaveModelSchemaV2 should transform scene to save model }, ], "title": "Test Panel", + "transparent": true, "vizConfig": { "kind": "timeseries", "spec": { diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts index 275a811998d..19a646f64e8 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts @@ -268,7 +268,6 @@ describe('transformSceneToSaveModelSchemaV2', () => { title: 'Test Panel 2', description: 'Test Description 2', fieldConfig: { defaults: {}, overrides: [] }, - displayMode: 'transparent', pluginVersion: '7.0.0', $timeRange: new SceneTimeRange({ timeZone: 'UTC', diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts index a4922c6cf94..426c1f5c7bc 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts @@ -220,6 +220,7 @@ export function vizPanelToSchemaV2( title: vizPanel.state.title, description: vizPanel.state.description ?? '', links: getPanelLinks(vizPanel), + transparent: vizPanel.state.displayMode === 'transparent' ? true : undefined, data: { kind: 'QueryGroup', spec: { From a8aa6b74a8ec34896958ec3964b08fd53c92fe82 Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Thu, 24 Apr 2025 13:14:21 +0100 Subject: [PATCH 087/146] FEMT: Basic frontend-service implementation (#104229) * create the most basic frontend-server module * expose prom metrics?? * add todo list * move frontend-service to its own folder in services * check error from writer.Write * reword comment, add launch config --- .github/CODEOWNERS | 1 + .vscode/launch.json | 10 ++ pkg/modules/dependencies.go | 2 + pkg/server/module_server.go | 7 +- pkg/services/frontend/frontend_service.go | 109 ++++++++++++++++++++++ 5 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 pkg/services/frontend/frontend_service.go diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 23e9fe99a3f..c8fdc572b60 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -138,6 +138,7 @@ /pkg/services/dashboardversion/ @grafana/grafana-backend-group /pkg/services/encryption/ @grafana/grafana-operator-experience-squad /pkg/services/folder/ @grafana/grafana-search-and-storage +/pkg/services/frontend/ @grafana/grafana-frontend-platform /pkg/services/apiserver @grafana/grafana-app-platform-squad /pkg/services/hooks/ @grafana/grafana-backend-group /pkg/services/kmsproviders/ @grafana/grafana-operator-experience-squad diff --git a/.vscode/launch.json b/.vscode/launch.json index 50393b02272..88a99c05737 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -82,6 +82,16 @@ "cwd": "${workspaceFolder}", "args": ["server", "target", "--homepath", "${workspaceFolder}", "--packaging", "dev"] }, + { + "name": "Run Frontend Server", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/pkg/cmd/grafana/", + "cwd": "${workspaceFolder}", + "env": { "GF_DEFAULT_TARGET": "frontend-server", "GF_SERVER_HTTP_PORT": "3003" }, + "args": ["server", "target", "--homepath", "${workspaceFolder}", "--packaging", "dev"] + }, { "name": "Attach to Chrome", "port": 9222, diff --git a/pkg/modules/dependencies.go b/pkg/modules/dependencies.go index f18b5f2c15f..7645386d823 100644 --- a/pkg/modules/dependencies.go +++ b/pkg/modules/dependencies.go @@ -9,6 +9,7 @@ const ( StorageServer string = "storage-server" ZanzanaServer string = "zanzana-server" InstrumentationServer string = "instrumentation-server" + FrontendServer string = "frontend-server" ) var dependencyMap = map[string][]string{ @@ -17,4 +18,5 @@ var dependencyMap = map[string][]string{ ZanzanaServer: {InstrumentationServer}, Core: {}, All: {Core}, + FrontendServer: {}, } diff --git a/pkg/server/module_server.go b/pkg/server/module_server.go index 11db7c204d3..7142b4b8c63 100644 --- a/pkg/server/module_server.go +++ b/pkg/server/module_server.go @@ -15,6 +15,7 @@ import ( "github.com/grafana/grafana/pkg/modules" "github.com/grafana/grafana/pkg/services/authz" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/frontend" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/sql" @@ -120,7 +121,7 @@ func (s *ModuleServer) Run() error { // only run the instrumentation server module if were not running a module that already contains an http server m.RegisterInvisibleModule(modules.InstrumentationServer, func() (services.Service, error) { - if m.IsModuleEnabled(modules.All) || m.IsModuleEnabled(modules.Core) { + if m.IsModuleEnabled(modules.All) || m.IsModuleEnabled(modules.Core) || m.IsModuleEnabled(modules.FrontendServer) { return services.NewBasicService(nil, nil, nil).WithName(modules.InstrumentationServer), nil } return NewInstrumentationService(s.log, s.cfg, s.promGatherer) @@ -151,6 +152,10 @@ func (s *ModuleServer) Run() error { return authz.ProvideZanzanaService(s.cfg, s.features) }) + m.RegisterModule(modules.FrontendServer, func() (services.Service, error) { + return frontend.ProvideFrontendService(s.cfg, s.promGatherer) + }) + m.RegisterModule(modules.All, nil) return m.Run(s.context) diff --git a/pkg/services/frontend/frontend_service.go b/pkg/services/frontend/frontend_service.go new file mode 100644 index 00000000000..3658d4097c0 --- /dev/null +++ b/pkg/services/frontend/frontend_service.go @@ -0,0 +1,109 @@ +package frontend + +import ( + "context" + "net" + "net/http" + "time" + + "github.com/grafana/dskit/services" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/setting" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +type frontendService struct { + *services.BasicService + cfg *setting.Cfg + httpServ *http.Server + log log.Logger + errChan chan error + promGatherer prometheus.Gatherer +} + +func ProvideFrontendService(cfg *setting.Cfg, promGatherer prometheus.Gatherer) (*frontendService, error) { + s := &frontendService{ + cfg: cfg, + log: log.New("frontend-server"), + promGatherer: promGatherer, + } + s.BasicService = services.NewBasicService(s.start, s.running, s.stop) + return s, nil +} + +func (s *frontendService) start(ctx context.Context) error { + s.httpServ = s.newFrontendServer(ctx) + s.errChan = make(chan error) + go func() { + s.errChan <- s.httpServ.ListenAndServe() + }() + return nil +} + +func (s *frontendService) running(ctx context.Context) error { + select { + case <-ctx.Done(): + return nil + case err := <-s.errChan: + return err + } +} + +func (s *frontendService) stop(failureReason error) error { + s.log.Info("stopping frontend server", "reason", failureReason) + if err := s.httpServ.Shutdown(context.Background()); err != nil { + s.log.Error("failed to shutdown frontend server", "error", err) + return err + } + return nil +} + +func (s *frontendService) newFrontendServer(ctx context.Context) *http.Server { + s.log.Info("starting frontend server", "addr", ":"+s.cfg.HTTPPort) + + router := http.NewServeMux() + router.Handle("/metrics", promhttp.HandlerFor(s.promGatherer, promhttp.HandlerOpts{EnableOpenMetrics: true})) + router.HandleFunc("/", s.handleRequest) + + server := &http.Server{ + // 5s timeout for header reads to avoid Slowloris attacks (https://thetooth.io/blog/slowloris-attack/) + ReadHeaderTimeout: 5 * time.Second, + Addr: ":" + s.cfg.HTTPPort, + Handler: router, + BaseContext: func(_ net.Listener) context.Context { return ctx }, + } + + return server +} + +func (s *frontendService) handleRequest(writer http.ResponseWriter, request *http.Request) { + // This should: + // - get correct asset urls from fs or cdn + // - generate a nonce + // - render them into the index.html + // - and return it to the user! + + s.log.Info("handling request", "method", request.Method, "url", request.URL.String()) + htmlContent := ` + + + Grafana Frontend Server + + + +

Grafana Frontend Server

+

This is a simple static HTML page served by the Grafana frontend server module.

+ +` + + writer.Header().Set("Content-Type", "text/html; charset=utf-8") + _, err := writer.Write([]byte(htmlContent)) + if err != nil { + s.log.Error("could not write to response", "err", err) + } +} From 35145801a2f41f0abc1d7fffc4aed5318fb80243 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Thu, 24 Apr 2025 13:26:28 +0100 Subject: [PATCH 088/146] Data Sources: Add gauge for response size (#104394) --- .../datasource_metrics_middleware.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/infra/httpclient/httpclientprovider/datasource_metrics_middleware.go b/pkg/infra/httpclient/httpclientprovider/datasource_metrics_middleware.go index 6334b373899..6a55c9f38ca 100644 --- a/pkg/infra/httpclient/httpclientprovider/datasource_metrics_middleware.go +++ b/pkg/infra/httpclient/httpclientprovider/datasource_metrics_middleware.go @@ -43,6 +43,14 @@ var ( }, []string{"datasource", "datasource_type", "secure_socks_ds_proxy_enabled"}, ) + datasourceResponseGauge = promauto.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: "plugins", + Name: "datasource_response_size", + Help: "gauge of external data source response sizes returned to Grafana in bytes", + }, []string{"datasource", "datasource_type", "secure_socks_ds_proxy_enabled"}, + ) + datasourceRequestsInFlight = promauto.NewGaugeVec( prometheus.GaugeOpts{ Namespace: "grafana", @@ -102,6 +110,7 @@ func executeMiddleware(next http.RoundTripper, labels prometheus.Labels) http.Ro requestHistogram := datasourceRequestHistogram.MustCurryWith(labels) requestInFlight := datasourceRequestsInFlight.With(labels) responseSizeHistogram := datasourceResponseHistogram.With(labels) + responseSizeGauge := datasourceResponseGauge.With(labels) res, err := promhttp.InstrumentRoundTripperDuration(requestHistogram, promhttp.InstrumentRoundTripperCounter(requestCounter, @@ -114,6 +123,7 @@ func executeMiddleware(next http.RoundTripper, labels prometheus.Labels) http.Ro if res != nil && res.StatusCode != http.StatusSwitchingProtocols { res.Body = sdkhttpclient.CountBytesReader(res.Body, func(bytesRead int64) { responseSizeHistogram.Observe(float64(bytesRead)) + responseSizeGauge.Set(float64(bytesRead)) }) } From 15bddb371217d00f96ba392c2f2f94e7d608b228 Mon Sep 17 00:00:00 2001 From: Eric Leijonmarck Date: Thu, 24 Apr 2025 13:39:31 +0100 Subject: [PATCH 089/146] IAM: Add `datasources:query` support for using the authlib/authzservice (#104107) * feat(add): datasources:query support for using the authlib/authzservice * added test for datasources * refactor to create the translation right away * Update pkg/services/authz/rbac/mapper.go Co-authored-by: Gabriel MABILLE * fix tests --------- Co-authored-by: Gabriel MABILLE --- pkg/apimachinery/identity/context.go | 1 + pkg/services/authz/rbac/mapper.go | 10 ++++++++++ pkg/services/authz/rbac/service_test.go | 20 ++++++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/pkg/apimachinery/identity/context.go b/pkg/apimachinery/identity/context.go index 08a3ed192b8..58ab1085d36 100644 --- a/pkg/apimachinery/identity/context.go +++ b/pkg/apimachinery/identity/context.go @@ -124,6 +124,7 @@ var serviceIdentityTokenPermissions = getTokenPermissions( "folder.grafana.app", "dashboard.grafana.app", "secret.grafana.app", + "query.grafana.app", ) var ServiceIdentityClaims = &authn.Claims[authn.AccessTokenClaims]{ diff --git a/pkg/services/authz/rbac/mapper.go b/pkg/services/authz/rbac/mapper.go index c930bb92fcb..023bfb6fc7a 100644 --- a/pkg/services/authz/rbac/mapper.go +++ b/pkg/services/authz/rbac/mapper.go @@ -67,6 +67,16 @@ func newMapper() mapper { "securevalues": newResourceTranslation("secret.securevalues", "uid", false), "keepers": newResourceTranslation("secret.keepers", "uid", false), }, + "query.grafana.app": { + "query": translation{ + resource: "datasources", + attribute: "uid", + verbMapping: map[string]string{ + utils.VerbCreate: "datasources:query", + }, + folderSupport: false, + }, + }, } } diff --git a/pkg/services/authz/rbac/service_test.go b/pkg/services/authz/rbac/service_test.go index 62c06f0e910..6a10dfed3c2 100644 --- a/pkg/services/authz/rbac/service_test.go +++ b/pkg/services/authz/rbac/service_test.go @@ -261,6 +261,26 @@ func TestService_checkPermission(t *testing.T) { }, expected: true, }, + { + name: "should return true for datasources if service has permission", + permissions: []accesscontrol.Permission{ + { + Action: "datasources:query", + Scope: "datasources:uid:some_datasource", + Kind: "datasources", + Attribute: "uid", + Identifier: "some_datasource", + }, + }, + check: CheckRequest{ + Action: "datasources:query", + Group: "query.grafana.app", + Resource: "query", + Name: "some_datasource", + Verb: utils.VerbCreate, + }, + expected: true, + }, } for _, tc := range testCases { From 7fd4c1b41d92bd30130a7317ab72075ec3a1e9a2 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Thu, 24 Apr 2025 15:42:32 +0300 Subject: [PATCH 090/146] StateTimeline: Auto-migrate from natel-discrete-panel (#104191) --- public/app/features/dashboard/state/PanelModel.ts | 5 +---- .../features/dashboard/state/getPanelPluginToMigrateTo.ts | 6 +----- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index 0933c48546f..5d16a8cce74 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -140,10 +140,7 @@ export const autoMigrateAngular: Record = { 'grafana-singlestat-panel': 'stat', 'grafana-piechart-panel': 'piechart', 'grafana-worldmap-panel': 'geomap', -}; - -export const autoMigrateRemovedPanelPlugins: Record = { - 'heatmap-new': 'heatmap', // this was a temporary development panel that is now standard + 'natel-discrete-panel': 'state-timeline', }; export class PanelModel implements DataConfigSource, IPanelModel { diff --git a/public/app/features/dashboard/state/getPanelPluginToMigrateTo.ts b/public/app/features/dashboard/state/getPanelPluginToMigrateTo.ts index c21959c86ef..114466c87ec 100644 --- a/public/app/features/dashboard/state/getPanelPluginToMigrateTo.ts +++ b/public/app/features/dashboard/state/getPanelPluginToMigrateTo.ts @@ -1,10 +1,6 @@ -import { autoMigrateRemovedPanelPlugins, autoMigrateAngular } from './PanelModel'; +import { autoMigrateAngular } from './PanelModel'; export function getPanelPluginToMigrateTo(panel: any): string | undefined { - if (autoMigrateRemovedPanelPlugins[panel.type]) { - return autoMigrateRemovedPanelPlugins[panel.type]; - } - // Graph needs special logic as it can be migrated to multiple panels if (panel.type === 'graph') { if (panel.xaxis?.mode === 'series') { From 4fc112f927d93ba86a3e1f2791c440a3b481ef8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 24 Apr 2025 14:43:44 +0200 Subject: [PATCH 091/146] RowsLayout: Fixes min height issue (#104444) --- .../dashboard-scene/scene/layout-rows/RowItemRenderer.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx index 5366860504d..a4de7c0a1ac 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx @@ -206,7 +206,6 @@ function getStyles(theme: GrafanaTheme2) { wrapper: css({ display: 'flex', flexDirection: 'column', - minHeight: '100px', }), wrapperNotCollapsed: css({ '> div:nth-child(2)': { From e385237daf44ac7d514422ce0d4f6e663888f680 Mon Sep 17 00:00:00 2001 From: Ieva Date: Thu, 24 Apr 2025 16:02:39 +0300 Subject: [PATCH 092/146] Access control: Make sure that user permission cache is cleared after new dashboard and folder creation (#104193) * make sure that user permission cache is cleared after new dashboard and folder creation * more test fixes * Update pkg/services/dashboards/service/dashboard_service.go * check identity type in SetDefaultPermissionsAfterCreate, set default permissions for service accounts * set SA permissions for folders as well * fix tests --- pkg/api/dashboard.go | 6 -- pkg/api/dashboard_test.go | 4 +- pkg/api/folder.go | 18 +++-- pkg/api/folder_bench_test.go | 5 +- pkg/registry/apis/folders/folder_storage.go | 13 +++- .../apis/folders/folder_storage_test.go | 2 + pkg/registry/apis/folders/register.go | 4 ++ .../accesscontrol/accesscontrol_test.go | 2 +- .../annotationsimpl/annotations_test.go | 4 +- .../dashboards/service/dashboard_service.go | 69 +++++++++---------- .../dashboard_service_integration_test.go | 9 ++- .../service/dashboard_service_test.go | 6 +- .../service/service_test.go | 7 +- pkg/services/folder/folderimpl/folder_test.go | 10 +-- .../libraryelements/libraryelements_test.go | 8 +-- .../librarypanels/librarypanels_test.go | 6 +- pkg/services/ngalert/testutil/testutil.go | 3 +- .../publicdashboards/api/query_test.go | 8 +-- .../publicdashboards/service/service_test.go | 2 +- pkg/services/quota/quotaimpl/quota_test.go | 3 +- 20 files changed, 99 insertions(+), 90 deletions(-) diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index b6a921d07c1..3ad8335151d 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -536,12 +536,6 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S return apierrors.ToDashboardErrorResponse(ctx, hs.pluginStore, saveErr) } - // Clear permission cache for the user who's created the dashboard, so that new permissions are fetched for their next call - // Required for cases when caller wants to immediately interact with the newly created object - if newDashboard { - hs.accesscontrolService.ClearUserPermissionCache(c.SignedInUser) - } - // connect library panels for this dashboard after the dashboard is stored and has an ID err = hs.LibraryPanelService.ConnectLibraryPanelsForDashboard(ctx, c.SignedInUser, dashboard) if err != nil { diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index 3909a70c561..687e09642f3 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -946,7 +946,7 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr if dashboardService == nil { dashboardService, err = service.ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, features, folderPermissions, - ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, + ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(db, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(), ) @@ -956,7 +956,7 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr dashboardProvisioningService, err := service.ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, features, folderPermissions, - ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, + ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(db, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(), ) diff --git a/pkg/api/folder.go b/pkg/api/folder.go index 02a8d95cb5c..2b03cba1397 100644 --- a/pkg/api/folder.go +++ b/pkg/api/folder.go @@ -206,10 +206,6 @@ func (hs *HTTPServer) CreateFolder(c *contextmodel.ReqContext) response.Response } } - // Clear permission cache for the user who's created the folder, so that new permissions are fetched for their next call - // Required for cases when caller wants to immediately interact with the newly created object - hs.accesscontrolService.ClearUserPermissionCache(c.SignedInUser) - folderDTO, err := hs.newToFolderDto(c, folder) if err != nil { return response.Err(err) @@ -226,7 +222,7 @@ func (hs *HTTPServer) setDefaultFolderPermissions(ctx context.Context, orgID int var permissions []accesscontrol.SetResourcePermissionCommand - if user.IsIdentityType(claims.TypeUser) { + if user.IsIdentityType(claims.TypeUser, claims.TypeServiceAccount) { userID, err := user.GetInternalID() if err != nil { return err @@ -246,7 +242,17 @@ func (hs *HTTPServer) setDefaultFolderPermissions(ctx context.Context, orgID int } _, err := hs.folderPermissionsService.SetPermissions(ctx, orgID, folder.UID, permissions...) - return err + if err != nil { + return err + } + + if user.IsIdentityType(claims.TypeUser, claims.TypeServiceAccount) { + // Clear permission cache for the user who's created the folder, so that new permissions are fetched for their next call + // Required for cases when caller wants to immediately interact with the newly created object + hs.accesscontrolService.ClearUserPermissionCache(user) + } + + return nil } // swagger:route POST /folders/{folder_uid}/move folders moveFolder diff --git a/pkg/api/folder_bench_test.go b/pkg/api/folder_bench_test.go index 222eaa6735a..f8d212a1d36 100644 --- a/pkg/api/folder_bench_test.go +++ b/pkg/api/folder_bench_test.go @@ -25,6 +25,7 @@ import ( "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" + "github.com/grafana/grafana/pkg/services/accesscontrol/actest" acdb "github.com/grafana/grafana/pkg/services/accesscontrol/database" "github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/permreg" @@ -477,8 +478,8 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog require.NoError(b, err) dashboardSvc, err := dashboardservice.ProvideDashboardServiceImpl( sc.cfg, dashStore, folderStore, - features, folderPermissions, ac, - folderServiceWithFlagOn, fStore, nil, client.MockTestRestConfig{}, nil, quotaSrv, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + features, folderPermissions, ac, actest.FakeService{}, + folderServiceWithFlagOn, nil, client.MockTestRestConfig{}, nil, quotaSrv, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(sc.db, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(), ) diff --git a/pkg/registry/apis/folders/folder_storage.go b/pkg/registry/apis/folders/folder_storage.go index 0a66772b651..588681e0311 100644 --- a/pkg/registry/apis/folders/folder_storage.go +++ b/pkg/registry/apis/folders/folder_storage.go @@ -38,6 +38,7 @@ type folderStorage struct { cfg *setting.Cfg features featuremgmt.FeatureToggles folderPermissionsSvc accesscontrol.FolderPermissionsService + acService accesscontrol.Service store grafanarest.Storage } @@ -139,7 +140,7 @@ func (s *folderStorage) setDefaultFolderPermissions(ctx context.Context, orgID i var permissions []accesscontrol.SetResourcePermissionCommand - if user.IsIdentityType(claims.TypeUser) { + if user.IsIdentityType(claims.TypeUser, claims.TypeServiceAccount) { userID, err := user.GetInternalID() if err != nil { return err @@ -157,5 +158,13 @@ func (s *folderStorage) setDefaultFolderPermissions(ctx context.Context, orgID i }...) } _, err := s.folderPermissionsSvc.SetPermissions(ctx, orgID, uid, permissions...) - return err + if err != nil { + return err + } + + if user.IsIdentityType(claims.TypeUser, claims.TypeServiceAccount) { + s.acService.ClearUserPermissionCache(user) + } + + return nil } diff --git a/pkg/registry/apis/folders/folder_storage_test.go b/pkg/registry/apis/folders/folder_storage_test.go index 73a1243d579..01527bb7eb0 100644 --- a/pkg/registry/apis/folders/folder_storage_test.go +++ b/pkg/registry/apis/folders/folder_storage_test.go @@ -16,6 +16,7 @@ import ( folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/accesscontrol/actest" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" @@ -48,6 +49,7 @@ func TestSetDefaultPermissionsWhenCreatingFolder(t *testing.T) { fs := folderStorage{ folderPermissionsSvc: folderPermService, + acService: actest.FakeService{}, store: &fakeStorage{}, cfg: cfg, } diff --git a/pkg/registry/apis/folders/register.go b/pkg/registry/apis/folders/register.go index b42d8746962..6c28909f63b 100644 --- a/pkg/registry/apis/folders/register.go +++ b/pkg/registry/apis/folders/register.go @@ -49,6 +49,7 @@ type FolderAPIBuilder struct { namespacer request.NamespaceMapper folderSvc folder.Service folderPermissionsSvc accesscontrol.FolderPermissionsService + acService accesscontrol.Service storage grafanarest.Storage authorizer authorizer.Authorizer @@ -64,6 +65,7 @@ func RegisterAPIService(cfg *setting.Cfg, folderSvc folder.Service, folderPermissionsSvc accesscontrol.FolderPermissionsService, accessControl accesscontrol.AccessControl, + acService accesscontrol.Service, registerer prometheus.Registerer, unified resource.ResourceClient, ) *FolderAPIBuilder { @@ -73,6 +75,7 @@ func RegisterAPIService(cfg *setting.Cfg, namespacer: request.GetNamespaceMapper(cfg), folderSvc: folderSvc, folderPermissionsSvc: folderPermissionsSvc, + acService: acService, cfg: cfg, authorizer: newLegacyAuthorizer(accessControl), searcher: unified, @@ -155,6 +158,7 @@ func (b *FolderAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.API folderStore := &folderStorage{ tableConverter: resourceInfo.TableConverter(), folderPermissionsSvc: b.folderPermissionsSvc, + acService: b.acService, features: b.features, cfg: b.cfg, } diff --git a/pkg/services/annotations/accesscontrol/accesscontrol_test.go b/pkg/services/annotations/accesscontrol/accesscontrol_test.go index 0ee98bda98d..f4ec20ffcfa 100644 --- a/pkg/services/annotations/accesscontrol/accesscontrol_test.go +++ b/pkg/services/annotations/accesscontrol/accesscontrol_test.go @@ -53,7 +53,7 @@ func TestIntegrationAuthorize(t *testing.T) { fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, nil, sql, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig) dashSvc, err := dashboardsservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), accesscontrolmock.NewMockedPermissionsService(), - ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(sql, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore()) require.NoError(t, err) diff --git a/pkg/services/annotations/annotationsimpl/annotations_test.go b/pkg/services/annotations/annotationsimpl/annotations_test.go index 32bc3f51ca3..19b3c1cbe2e 100644 --- a/pkg/services/annotations/annotationsimpl/annotations_test.go +++ b/pkg/services/annotations/annotationsimpl/annotations_test.go @@ -66,7 +66,7 @@ func TestIntegrationAnnotationListingWithRBAC(t *testing.T) { fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, nil, sql, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig) dashSvc, err := dashboardsservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), accesscontrolmock.NewMockedPermissionsService(), - ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(sql, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore()) require.NoError(t, err) @@ -251,7 +251,7 @@ func TestIntegrationAnnotationListingWithInheritedRBAC(t *testing.T) { fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, nil, sql, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig) dashSvc, err := dashboardsservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, features, accesscontrolmock.NewMockedPermissionsService(), - ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(sql, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(), ) diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index 9466d45ea0d..e9e3f695850 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -89,6 +89,7 @@ type DashboardServiceImpl struct { folderPermissions accesscontrol.FolderPermissionsService dashboardPermissions accesscontrol.DashboardPermissionsService ac accesscontrol.AccessControl + acService accesscontrol.Service k8sclient client.K8sHandler metrics *dashboardsMetrics publicDashboardService publicdashboards.ServiceWrapper @@ -375,7 +376,7 @@ var _ registry.BackgroundService = (*DashboardServiceImpl)(nil) func ProvideDashboardServiceImpl( cfg *setting.Cfg, dashboardStore dashboards.Store, folderStore folder.FolderStore, features featuremgmt.FeatureToggles, folderPermissionsService accesscontrol.FolderPermissionsService, - ac accesscontrol.AccessControl, folderSvc folder.Service, fStore folder.Store, r prometheus.Registerer, + ac accesscontrol.AccessControl, acService accesscontrol.Service, folderSvc folder.Service, r prometheus.Registerer, restConfigProvider apiserver.RestConfigProvider, userService user.Service, quotaService quota.Service, orgService org.Service, publicDashboardService publicdashboards.ServiceWrapper, resourceClient resource.ResourceClient, dual dualwrite.Service, sorter sort.Service, @@ -390,6 +391,7 @@ func ProvideDashboardServiceImpl( features: features, folderPermissions: folderPermissionsService, ac: ac, + acService: acService, folderStore: folderStore, folderService: folderSvc, orgService: orgService, @@ -1009,7 +1011,7 @@ func (dr *DashboardServiceImpl) SaveFolderForProvisionedDashboards(ctx context.C // Only set default permissions if the Folder API Server is disabled. if !dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { - dr.setDefaultFolderPermissions(ctx, dto, f, true) + dr.setDefaultFolderPermissions(ctx, dto, f) } return f, nil } @@ -1214,16 +1216,15 @@ func (dr *DashboardServiceImpl) SetDefaultPermissionsAfterCreate(ctx context.Con if err != nil { return err } - var permissions []accesscontrol.SetResourcePermissionCommand + permissions := []accesscontrol.SetResourcePermissionCommand{} + if user.IsIdentityType(claims.TypeUser, claims.TypeServiceAccount) { + permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{ + UserID: uid, Permission: dashboardaccess.PERMISSION_ADMIN.String(), + }) + } + isNested := obj.GetFolder() != "" if !dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesDashboards) { // legacy behavior - permissions = []accesscontrol.SetResourcePermissionCommand{} - if user.IsIdentityType(claims.TypeUser) { - permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{ - UserID: uid, Permission: dashboardaccess.PERMISSION_ADMIN.String(), - }) - } - isNested := obj.GetFolder() != "" if !isNested || !dr.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) { permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{ {BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()}, @@ -1231,14 +1232,14 @@ func (dr *DashboardServiceImpl) SetDefaultPermissionsAfterCreate(ctx context.Con }...) } } else { - if obj.GetFolder() != "" { + // Don't set any permissions for nested dashboards + if isNested { return nil } - permissions = []accesscontrol.SetResourcePermissionCommand{ - {UserID: uid, Permission: dashboardaccess.PERMISSION_ADMIN.String()}, + permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{ {BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, {BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()}, - } + }...) } svc := dr.getPermissionsService(key.Resource == "folders") if _, err := svc.SetPermissions(ctx, ns.OrgID, obj.GetName(), permissions...); err != nil { @@ -1246,6 +1247,12 @@ func (dr *DashboardServiceImpl) SetDefaultPermissionsAfterCreate(ctx context.Con return err } + // Clear permission cache for the user who created the dashboard, so that new permissions are fetched for their next call + // Required for cases when caller wants to immediately interact with the newly created object + if user.IsIdentityType(claims.TypeUser, claims.TypeServiceAccount) { + dr.acService.ClearUserPermissionCache(user) + } + return nil } @@ -1287,37 +1294,25 @@ func (dr *DashboardServiceImpl) SetDefaultPermissions(ctx context.Context, dto * if _, err := svc.SetPermissions(ctx, dto.OrgID, dash.UID, permissions...); err != nil { dr.log.Error("Could not set default permissions", "dashboard", dash.Title, "error", err) } + + // Clear permission cache for the user who created the dashboard, so that new permissions are fetched for their next call + // Required for cases when caller wants to immediately interact with the newly created object + if !provisioned && dto.User.IsIdentityType(claims.TypeUser, claims.TypeServiceAccount) { + dr.acService.ClearUserPermissionCache(dto.User) + } } -func (dr *DashboardServiceImpl) setDefaultFolderPermissions(ctx context.Context, cmd *folder.CreateFolderCommand, f *folder.Folder, provisioned bool) { - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { +func (dr *DashboardServiceImpl) setDefaultFolderPermissions(ctx context.Context, cmd *folder.CreateFolderCommand, f *folder.Folder) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) || !dr.cfg.RBAC.PermissionsOnCreation("folder") || f.ParentUID != "" { return } ctx, span := tracer.Start(ctx, "dashboards.service.setDefaultFolderPermissions") defer span.End() - if !dr.cfg.RBAC.PermissionsOnCreation("folder") { - return - } - - var permissions []accesscontrol.SetResourcePermissionCommand - if !provisioned && cmd.SignedInUser.IsIdentityType(claims.TypeUser) { - userID, err := cmd.SignedInUser.GetInternalID() - if err != nil { - dr.log.Error("Could not make user admin", "folder", cmd.Title, "id", cmd.SignedInUser.GetID()) - } else { - permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{ - UserID: userID, Permission: dashboardaccess.PERMISSION_ADMIN.String(), - }) - } - } - - if f.ParentUID == "" { - permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{ - {BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()}, - {BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()}, - }...) + permissions := []accesscontrol.SetResourcePermissionCommand{ + {BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()}, + {BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()}, } if _, err := dr.folderPermissions.SetPermissions(ctx, cmd.OrgID, f.UID, permissions...); err != nil { diff --git a/pkg/services/dashboards/service/dashboard_service_integration_test.go b/pkg/services/dashboards/service/dashboard_service_integration_test.go index eb116482bdb..38c11736a1b 100644 --- a/pkg/services/dashboards/service/dashboard_service_integration_test.go +++ b/pkg/services/dashboards/service/dashboard_service_integration_test.go @@ -23,7 +23,6 @@ import ( "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards/database" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder/folderimpl" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/publicdashboards" @@ -806,8 +805,8 @@ func permissionScenario(t *testing.T, desc string, fn permissionScenarioFunc) { featuremgmt.WithFeatures(), folderPermissions, ac, + actest.FakeService{}, folderService, - folder.NewFakeStore(), nil, client.MockTestRestConfig{}, nil, @@ -902,8 +901,8 @@ func callSaveWithResult(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSt featuremgmt.WithFeatures(), folderPermissions, ac, + actest.FakeService{}, folderService, - folder.NewFakeStore(), nil, client.MockTestRestConfig{}, nil, @@ -976,8 +975,8 @@ func saveTestDashboard(t *testing.T, title string, orgID int64, folderUID string features, accesscontrolmock.NewMockedPermissionsService(), actest.FakeAccessControl{ExpectedEvaluate: true}, + actest.FakeService{}, folderService, - folder.NewFakeStore(), nil, client.MockTestRestConfig{}, nil, @@ -1058,8 +1057,8 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore db.DB) *da featuremgmt.WithFeatures(), folderPermissions, actest.FakeAccessControl{ExpectedEvaluate: true}, + actest.FakeService{}, folderService, - folder.NewFakeStore(), nil, client.MockTestRestConfig{}, nil, diff --git a/pkg/services/dashboards/service/dashboard_service_test.go b/pkg/services/dashboards/service/dashboard_service_test.go index 2861055a667..15179f52a2e 100644 --- a/pkg/services/dashboards/service/dashboard_service_test.go +++ b/pkg/services/dashboards/service/dashboard_service_test.go @@ -1288,8 +1288,9 @@ func TestSetDefaultPermissionsWhenSavingFolderForProvisionedDashboards(t *testin UID: "general", }, }, - ac: actest.FakeAccessControl{ExpectedEvaluate: true}, - log: log.NewNopLogger(), + ac: actest.FakeAccessControl{ExpectedEvaluate: true}, + acService: &actest.FakeService{}, + log: log.NewNopLogger(), } cmd := &folder.CreateFolderCommand{ @@ -2497,6 +2498,7 @@ func TestSetDefaultPermissionsAfterCreate(t *testing.T) { dashboardPermissions: permService, folderPermissions: permService, dashboardPermissionsReady: make(chan struct{}), + acService: &actest.FakeService{}, } service.RegisterDashboardPermissions(permService) diff --git a/pkg/services/dashboardsnapshots/service/service_test.go b/pkg/services/dashboardsnapshots/service/service_test.go index c647528785b..bdfc8b0d8d8 100644 --- a/pkg/services/dashboardsnapshots/service/service_test.go +++ b/pkg/services/dashboardsnapshots/service/service_test.go @@ -14,7 +14,7 @@ import ( "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/infra/tracing" - acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" + "github.com/grafana/grafana/pkg/services/accesscontrol/actest" "github.com/grafana/grafana/pkg/services/apiserver/client" "github.com/grafana/grafana/pkg/services/dashboards" dashdb "github.com/grafana/grafana/pkg/services/dashboards/database" @@ -22,7 +22,6 @@ import ( "github.com/grafana/grafana/pkg/services/dashboardsnapshots" dashsnapdb "github.com/grafana/grafana/pkg/services/dashboardsnapshots/database" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder/folderimpl" "github.com/grafana/grafana/pkg/services/folder/foldertest" "github.com/grafana/grafana/pkg/services/quota/quotatest" @@ -112,9 +111,9 @@ func TestValidateDashboardExists(t *testing.T) { folderimpl.ProvideDashboardFolderStore(sqlStore), feats, nil, - acmock.New(), + actest.FakeAccessControl{}, + actest.FakeService{}, foldertest.NewFakeService(), - folder.NewFakeStore(), nil, client.MockTestRestConfig{}, nil, diff --git a/pkg/services/folder/folderimpl/folder_test.go b/pkg/services/folder/folderimpl/folder_test.go index 15f48dbcce4..8665b079bcc 100644 --- a/pkg/services/folder/folderimpl/folder_test.go +++ b/pkg/services/folder/folderimpl/folder_test.go @@ -443,7 +443,7 @@ func TestIntegrationNestedFolderService(t *testing.T) { }) publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil) - dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuresFlagOn, folderPermissions, ac, serviceWithFlagOn, nestedFolderStore, nil, + dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuresFlagOn, folderPermissions, ac, actest.FakeService{}, serviceWithFlagOn, nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(db, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(), @@ -533,7 +533,7 @@ func TestIntegrationNestedFolderService(t *testing.T) { publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil) dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuresFlagOff, - folderPermissions, ac, serviceWithFlagOff, nestedFolderStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + folderPermissions, ac, actest.FakeService{}, serviceWithFlagOff, nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(db, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(), ) @@ -679,8 +679,8 @@ func TestIntegrationNestedFolderService(t *testing.T) { tc.service.store = nestedFolderStore publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil) - dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, tc.featuresFlag, folderPermissions, ac, tc.service, - tc.service.store, nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil, + dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, tc.featuresFlag, folderPermissions, ac, actest.FakeService{}, tc.service, + nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(db, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(), @@ -1465,8 +1465,8 @@ func TestIntegrationNestedFolderSharedWithMe(t *testing.T) { featuresFlagOn, acmock.NewMockedPermissionsService(), actest.FakeAccessControl{}, + actest.FakeService{}, serviceWithFlagOn, - nestedFolderStore, nil, client.MockTestRestConfig{}, nil, diff --git a/pkg/services/libraryelements/libraryelements_test.go b/pkg/services/libraryelements/libraryelements_test.go index 95dc2f48dfe..2d99b5edad1 100644 --- a/pkg/services/libraryelements/libraryelements_test.go +++ b/pkg/services/libraryelements/libraryelements_test.go @@ -358,8 +358,8 @@ func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash service, err := dashboardservice.ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, features, folderPermissions, ac, + actest.FakeService{}, folderSvc, - folder.NewFakeStore(), nil, client.MockTestRestConfig{}, nil, @@ -461,8 +461,7 @@ func scenarioWithPanel(t *testing.T, desc string, fn func(t *testing.T, sc scena nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig) dashboardService, svcErr := dashboardservice.ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, - features, folderPermissions, ac, - folderSvc, fStore, + features, folderPermissions, ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(), @@ -536,8 +535,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo require.NoError(t, err) dashService, dashSvcErr := dashboardservice.ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, - features, folderPermissions, ac, - folderSvc, fStore, + features, folderPermissions, ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(), diff --git a/pkg/services/librarypanels/librarypanels_test.go b/pkg/services/librarypanels/librarypanels_test.go index 4a111d204a8..35910fce92b 100644 --- a/pkg/services/librarypanels/librarypanels_test.go +++ b/pkg/services/librarypanels/librarypanels_test.go @@ -739,8 +739,7 @@ func createDashboard(t *testing.T, sqlStore db.DB, user *user.SignedInUser, dash dashPermissionService.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil) service, err := dashboardservice.ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, - features, acmock.NewMockedPermissionsService(), ac, - foldertest.NewFakeService(), folder.NewFakeStore(), + features, acmock.NewMockedPermissionsService(), ac, actest.FakeService{}, foldertest.NewFakeService(), nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore()) @@ -838,8 +837,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo folderSvc.ExpectedFolder = &folder.Folder{ID: 1} dashService, err := dashboardservice.ProvideDashboardServiceImpl( cfg, dashStore, folderStore, - features, acmock.NewMockedPermissionsService(), ac, - folderSvc, folder.NewFakeStore(), + features, acmock.NewMockedPermissionsService(), ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore()) diff --git a/pkg/services/ngalert/testutil/testutil.go b/pkg/services/ngalert/testutil/testutil.go index e59a676126c..4618de2ea9d 100644 --- a/pkg/services/ngalert/testutil/testutil.go +++ b/pkg/services/ngalert/testutil/testutil.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/accesscontrol/actest" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "github.com/grafana/grafana/pkg/services/apiserver" "github.com/grafana/grafana/pkg/services/apiserver/client" @@ -67,7 +68,7 @@ func SetupDashboardService(tb testing.TB, sqlStore db.DB, fs *folderimpl.Dashboa dashboardService, err := dashboardservice.ProvideDashboardServiceImpl( cfg, dashboardStore, fs, features, folderPermissions, ac, - foldertest.NewFakeService(), folder.NewFakeStore(), + &actest.FakeService{}, foldertest.NewFakeService(), nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()), diff --git a/pkg/services/publicdashboards/api/query_test.go b/pkg/services/publicdashboards/api/query_test.go index e0215125c4b..905e7e2cb4f 100644 --- a/pkg/services/publicdashboards/api/query_test.go +++ b/pkg/services/publicdashboards/api/query_test.go @@ -26,6 +26,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/services/accesscontrol/actest" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "github.com/grafana/grafana/pkg/services/annotations/annotationstest" "github.com/grafana/grafana/pkg/services/apiserver/client" @@ -36,7 +37,6 @@ import ( "github.com/grafana/grafana/pkg/services/datasources/guardian" datasourcesService "github.com/grafana/grafana/pkg/services/datasources/service" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder/folderimpl" "github.com/grafana/grafana/pkg/services/folder/foldertest" "github.com/grafana/grafana/pkg/services/licensing/licensingtest" @@ -324,14 +324,14 @@ func TestIntegrationUnauthenticatedUserCanGetPubdashPanelQueryData(t *testing.T) // create public dashboard store := publicdashboardsStore.ProvideStore(db, cfg, featuremgmt.WithFeatures()) cfg.PublicDashboardsEnabled = true - ac := acmock.New() + ac := actest.FakeAccessControl{} ws := publicdashboardsService.ProvideServiceWrapper(store) folderStore := folderimpl.ProvideDashboardFolderStore(db) dashPermissionService := acmock.NewMockedPermissionsService() dashService, err := service.ProvideDashboardServiceImpl( cfg, dashboardStoreService, folderStore, - featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), ac, - foldertest.NewFakeService(), folder.NewFakeStore(), nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, + featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), ac, actest.FakeService{}, + foldertest.NewFakeService(), nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(db, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(), diff --git a/pkg/services/publicdashboards/service/service_test.go b/pkg/services/publicdashboards/service/service_test.go index ac3db3d394c..9ae9d27bd16 100644 --- a/pkg/services/publicdashboards/service/service_test.go +++ b/pkg/services/publicdashboards/service/service_test.go @@ -1403,7 +1403,7 @@ func TestPublicDashboardServiceImpl_ListPublicDashboards(t *testing.T) { fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, nil, testDB, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig) - dashboardService, err := dashsvc.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), folderPermissions, ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + dashboardService, err := dashsvc.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), folderPermissions, ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(testDB, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore()) require.NoError(t, err) diff --git a/pkg/services/quota/quotaimpl/quota_test.go b/pkg/services/quota/quotaimpl/quota_test.go index 571e47f7aa1..6fca4b44c0a 100644 --- a/pkg/services/quota/quotaimpl/quota_test.go +++ b/pkg/services/quota/quotaimpl/quota_test.go @@ -18,6 +18,7 @@ import ( "github.com/grafana/grafana/pkg/infra/tracing" pluginfakes "github.com/grafana/grafana/pkg/plugins/manager/fakes" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" + "github.com/grafana/grafana/pkg/services/accesscontrol/actest" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "github.com/grafana/grafana/pkg/services/annotations/annotationstest" "github.com/grafana/grafana/pkg/services/apikey" @@ -504,7 +505,7 @@ func setupEnv(t *testing.T, sqlStore db.DB, cfg *setting.Cfg, b bus.Bus, quotaSe fStore, acmock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, nil, sqlStore, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig) dashService, err := dashService.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), - ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore()) require.NoError(t, err) From 2e51096eb45500570990a4fc3a625464edf26f04 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Thu, 24 Apr 2025 16:16:25 +0300 Subject: [PATCH 093/146] Provisioning: Export dashboards with conversion errors (#104369) --- .../provisioning/jobs/export/folders_test.go | 7 + .../provisioning/jobs/export/resources.go | 69 ++- .../jobs/export/resources_test.go | 413 ++++++++++++------ .../apis/provisioning/resources/client.go | 10 +- .../dashboard-test-v0.yaml | 8 + .../dashboard-test-v1.yaml | 9 + .../dashboard-test-v2.yaml | 10 + .../root_dashboard.json | 162 ------- pkg/tests/apis/provisioning/helper_test.go | 28 +- .../apis/provisioning/provisioning_test.go | 75 +++- 10 files changed, 453 insertions(+), 338 deletions(-) create mode 100644 pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v0.yaml create mode 100644 pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v1.yaml create mode 100644 pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v2.yaml delete mode 100644 pkg/tests/apis/provisioning/exportunifiedtorepository/root_dashboard.json diff --git a/pkg/registry/apis/provisioning/jobs/export/folders_test.go b/pkg/registry/apis/provisioning/jobs/export/folders_test.go index ac2c1aa13e4..bec0214d7ef 100644 --- a/pkg/registry/apis/provisioning/jobs/export/folders_test.go +++ b/pkg/registry/apis/provisioning/jobs/export/folders_test.go @@ -510,3 +510,10 @@ func (m *mockDynamicInterface) List(ctx context.Context, opts metav1.ListOptions func (m *mockDynamicInterface) Delete(ctx context.Context, name string, opts metav1.DeleteOptions, subresources ...string) error { return m.deleteError } + +func (m *mockDynamicInterface) Get(ctx context.Context, name string, opts metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) { + if len(m.items) == 0 { + return nil, fmt.Errorf("no items found") + } + return &m.items[0], nil +} diff --git a/pkg/registry/apis/provisioning/jobs/export/resources.go b/pkg/registry/apis/provisioning/jobs/export/resources.go index a401615c427..c469f4def8c 100644 --- a/pkg/registry/apis/provisioning/jobs/export/resources.go +++ b/pkg/registry/apis/provisioning/jobs/export/resources.go @@ -4,7 +4,9 @@ import ( "context" "errors" "fmt" + "strings" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/client-go/dynamic" @@ -14,6 +16,11 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" ) +// FIXME: This is used to make sure we save dashboards in the apiVersion they were original saved in +// When requesting v0 or v2 dashboards over the v1 api -- the backend tries (and fails!) to convert values +// The response status indicates the original stored version, so we can then request it in an un-converted form +type conversionShim = func(ctx context.Context, item *unstructured.Unstructured) (*unstructured.Unstructured, error) + func ExportResources(ctx context.Context, options provisioning.ExportJobOptions, clients resources.ResourceClients, repositoryResources resources.RepositoryResources, progress jobs.JobProgressRecorder) error { progress.SetMessage(ctx, "start resource export") for _, kind := range resources.SupportedProvisioningResources { @@ -28,7 +35,39 @@ func ExportResources(ctx context.Context, options provisioning.ExportJobOptions, return fmt.Errorf("get client for %s: %w", kind.Resource, err) } - if err := exportResource(ctx, options, client, repositoryResources, progress); err != nil { + // When requesting v2 (or v0) dashboards over the v1 api, we want to keep the original apiVersion if conversion fails + var shim conversionShim + if kind.GroupResource() == resources.DashboardResource.GroupResource() { + var v2client dynamic.ResourceInterface + shim = func(ctx context.Context, item *unstructured.Unstructured) (*unstructured.Unstructured, error) { + failed, _, _ := unstructured.NestedBool(item.Object, "status", "conversion", "failed") + if failed { + storedVersion, _, _ := unstructured.NestedString(item.Object, "status", "conversion", "storedVersion") + + // For v2 we need to request the original version + if strings.HasPrefix(storedVersion, "v2") { + if v2client == nil { + v2client, _, err = clients.ForResource(resources.DashboardResourceV2) + if err != nil { + return nil, err + } + } + return v2client.Get(ctx, item.GetName(), metav1.GetOptions{}) + } + + // For v0 we can simply fallback -- the full model is saved, but + if strings.HasPrefix(storedVersion, "v0") { + item.SetAPIVersion(fmt.Sprintf("%s/%s", kind.Group, storedVersion)) + return item, nil + } + + return nil, fmt.Errorf("unsupported dashboard version: %s", storedVersion) + } + return item, nil + } + } + + if err := exportResource(ctx, options, client, shim, repositoryResources, progress); err != nil { return fmt.Errorf("export %s: %w", kind.Resource, err) } } @@ -36,20 +75,32 @@ func ExportResources(ctx context.Context, options provisioning.ExportJobOptions, return nil } -func exportResource(ctx context.Context, options provisioning.ExportJobOptions, client dynamic.ResourceInterface, repositoryResources resources.RepositoryResources, progress jobs.JobProgressRecorder) error { - return resources.ForEach(ctx, client, func(item *unstructured.Unstructured) error { - fileName, err := repositoryResources.WriteResourceFileFromObject(ctx, item, resources.WriteOptions{ - Path: options.Path, - Ref: options.Branch, - }) - +func exportResource(ctx context.Context, + options provisioning.ExportJobOptions, + client dynamic.ResourceInterface, + shim conversionShim, + repositoryResources resources.RepositoryResources, + progress jobs.JobProgressRecorder, +) error { + // FIXME: using k8s list will force evrything into one version -- we really want the original saved version + // this will work well enough for now, but needs to be revisted as we have a bigger mix of active versions + return resources.ForEach(ctx, client, func(item *unstructured.Unstructured) (err error) { gvk := item.GroupVersionKind() result := jobs.JobResourceResult{ Name: item.GetName(), Resource: gvk.Kind, Group: gvk.Group, Action: repository.FileActionCreated, - Path: fileName, + } + + if shim != nil { + item, err = shim(ctx, item) + } + if err == nil { + result.Path, err = repositoryResources.WriteResourceFileFromObject(ctx, item, resources.WriteOptions{ + Path: options.Path, + Ref: options.Branch, + }) } if errors.Is(err, resources.ErrAlreadyInRepository) { diff --git a/pkg/registry/apis/provisioning/jobs/export/resources_test.go b/pkg/registry/apis/provisioning/jobs/export/resources_test.go index d1a75443316..64f7d256866 100644 --- a/pkg/registry/apis/provisioning/jobs/export/resources_test.go +++ b/pkg/registry/apis/provisioning/jobs/export/resources_test.go @@ -7,12 +7,8 @@ import ( mock "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - dynamicfake "k8s.io/client-go/dynamic/fake" - k8testing "k8s.io/client-go/testing" provisioningV0 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs" @@ -20,44 +16,35 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" ) -func TestExportResources(t *testing.T) { +func TestExportResources_Dashboards(t *testing.T) { tests := []struct { name string - reactorFunc func(action k8testing.Action) (bool, runtime.Object, error) + mockItems []unstructured.Unstructured expectedError string setupProgress func(progress *jobs.MockJobProgressRecorder) - setupResources func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, dynamicClient *dynamicfake.FakeDynamicClient, gvk schema.GroupVersionKind) + setupResources func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) }{ { name: "successful dashboard export", - reactorFunc: func(action k8testing.Action) (bool, runtime.Object, error) { - // Return dashboard list - return true, &metav1.PartialObjectMetadataList{ - TypeMeta: metav1.TypeMeta{ - APIVersion: resources.DashboardResource.GroupVersion().String(), - Kind: "DashboardList", - }, - Items: []metav1.PartialObjectMetadata{ - { - TypeMeta: metav1.TypeMeta{ - APIVersion: resources.DashboardResource.GroupVersion().String(), - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "dashboard-1", - }, - }, - { - TypeMeta: metav1.TypeMeta{ - APIVersion: resources.DashboardResource.GroupVersion().String(), - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "dashboard-2", - }, + mockItems: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "dashboard-1", }, }, - }, nil + }, + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "dashboard-2", + }, + }, + }, }, expectedError: "", setupProgress: func(progress *jobs.MockJobProgressRecorder) { @@ -72,8 +59,8 @@ func TestExportResources(t *testing.T) { progress.On("TooManyErrors").Return(nil) progress.On("TooManyErrors").Return(nil) }, - setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, dynamicClient *dynamicfake.FakeDynamicClient, gvk schema.GroupVersionKind) { - resourceClients.On("ForResource", resources.DashboardResource).Return(dynamicClient.Resource(resources.DashboardResource), gvk, nil) + setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) options := resources.WriteOptions{ Path: "grafana", Ref: "feature/branch", @@ -89,62 +76,38 @@ func TestExportResources(t *testing.T) { }, }, { - name: "client error", - reactorFunc: func(action k8testing.Action) (bool, runtime.Object, error) { - return true, nil, fmt.Errorf("shouldn't happen") - }, + name: "client error", + mockItems: nil, expectedError: "get client for dashboards: didn't work", setupProgress: func(progress *jobs.MockJobProgressRecorder) { progress.On("SetMessage", mock.Anything, "start resource export").Return() progress.On("SetMessage", mock.Anything, "export dashboards").Return() }, - setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, dynamicClient *dynamicfake.FakeDynamicClient, gvk schema.GroupVersionKind) { - resourceClients.On("ForResource", resources.DashboardResource).Return(dynamicClient.Resource(resources.DashboardResource), gvk, fmt.Errorf("didn't work")) - }, - }, - { - name: "dashboard list error", - reactorFunc: func(action k8testing.Action) (bool, runtime.Object, error) { - return true, nil, fmt.Errorf("failed to list dashboards") - }, - expectedError: "export dashboards: error executing list: failed to list dashboards", - setupProgress: func(progress *jobs.MockJobProgressRecorder) { - progress.On("SetMessage", mock.Anything, "start resource export").Return() - progress.On("SetMessage", mock.Anything, "export dashboards").Return() - }, - setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, dynamicClient *dynamicfake.FakeDynamicClient, gvk schema.GroupVersionKind) { - resourceClients.On("ForResource", resources.DashboardResource).Return(dynamicClient.Resource(resources.DashboardResource), gvk, nil) + setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, fmt.Errorf("didn't work")) }, }, { name: "dashboard export with errors", - reactorFunc: func(action k8testing.Action) (bool, runtime.Object, error) { - return true, &metav1.PartialObjectMetadataList{ - TypeMeta: metav1.TypeMeta{ - APIVersion: resources.DashboardResource.GroupVersion().String(), - Kind: "DashboardList", - }, - Items: []metav1.PartialObjectMetadata{ - { - TypeMeta: metav1.TypeMeta{ - APIVersion: resources.DashboardResource.GroupVersion().String(), - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "dashboard-1", - }, - }, - { - TypeMeta: metav1.TypeMeta{ - APIVersion: resources.DashboardResource.GroupVersion().String(), - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "dashboard-2", - }, + mockItems: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "dashboard-1", }, }, - }, nil + }, + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "dashboard-2", + }, + }, + }, }, expectedError: "", setupProgress: func(progress *jobs.MockJobProgressRecorder) { @@ -159,8 +122,8 @@ func TestExportResources(t *testing.T) { progress.On("TooManyErrors").Return(nil) progress.On("TooManyErrors").Return(nil) }, - setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, dynamicClient *dynamicfake.FakeDynamicClient, gvk schema.GroupVersionKind) { - resourceClients.On("ForResource", resources.DashboardResource).Return(dynamicClient.Resource(resources.DashboardResource), gvk, nil) + setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) options := resources.WriteOptions{ Path: "grafana", Ref: "feature/branch", @@ -177,24 +140,16 @@ func TestExportResources(t *testing.T) { }, { name: "dashboard export too many errors", - reactorFunc: func(action k8testing.Action) (bool, runtime.Object, error) { - return true, &metav1.PartialObjectMetadataList{ - TypeMeta: metav1.TypeMeta{ - APIVersion: resources.DashboardResource.GroupVersion().String(), - Kind: "DashboardList", - }, - Items: []metav1.PartialObjectMetadata{ - { - TypeMeta: metav1.TypeMeta{ - APIVersion: resources.DashboardResource.GroupVersion().String(), - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "dashboard-1", - }, + mockItems: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "dashboard-1", }, }, - }, nil + }, }, expectedError: "export dashboards: too many errors encountered", setupProgress: func(progress *jobs.MockJobProgressRecorder) { @@ -205,8 +160,8 @@ func TestExportResources(t *testing.T) { })).Return() progress.On("TooManyErrors").Return(fmt.Errorf("too many errors encountered")) }, - setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, dynamicClient *dynamicfake.FakeDynamicClient, gvk schema.GroupVersionKind) { - resourceClients.On("ForResource", resources.DashboardResource).Return(dynamicClient.Resource(resources.DashboardResource), gvk, nil) + setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) options := resources.WriteOptions{ Path: "grafana", Ref: "feature/branch", @@ -219,24 +174,16 @@ func TestExportResources(t *testing.T) { }, { name: "ignores existing dashboards", - reactorFunc: func(action k8testing.Action) (bool, runtime.Object, error) { - return true, &metav1.PartialObjectMetadataList{ - TypeMeta: metav1.TypeMeta{ - APIVersion: resources.DashboardResource.GroupVersion().String(), - Kind: "DashboardList", - }, - Items: []metav1.PartialObjectMetadata{ - { - TypeMeta: metav1.TypeMeta{ - APIVersion: resources.DashboardResource.GroupVersion().String(), - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "existing-dashboard", - }, + mockItems: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "existing-dashboard", }, }, - }, nil + }, }, expectedError: "", setupProgress: func(progress *jobs.MockJobProgressRecorder) { @@ -247,50 +194,238 @@ func TestExportResources(t *testing.T) { })).Return() progress.On("TooManyErrors").Return(nil) }, - setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, dynamicClient *dynamicfake.FakeDynamicClient, gvk schema.GroupVersionKind) { - resourceClients.On("ForResource", resources.DashboardResource).Return(dynamicClient.Resource(resources.DashboardResource), gvk, nil) + setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) options := resources.WriteOptions{ Path: "grafana", Ref: "feature/branch", } - // Return true to indicate the file already exists, and provide the updated path repoResources.On("WriteResourceFileFromObject", mock.Anything, mock.MatchedBy(func(obj *unstructured.Unstructured) bool { return obj.GetName() == "existing-dashboard" }), options).Return("", resources.ErrAlreadyInRepository) }, }, + { + name: "uses saved dashboard version", + mockItems: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "existing-dashboard", + }, + "spec": map[string]interface{}{ + "hello": "world", + }, + "status": map[string]interface{}{ + "conversion": map[string]interface{}{ + "failed": true, + "storedVersion": "v0xyz", + }, + }, + }, + }, + }, + expectedError: "", + setupProgress: func(progress *jobs.MockJobProgressRecorder) { + progress.On("SetMessage", mock.Anything, "start resource export").Return() + progress.On("SetMessage", mock.Anything, "export dashboards").Return() + progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + return result.Name == "existing-dashboard" && result.Action == repository.FileActionIgnored + })).Return() + progress.On("TooManyErrors").Return(nil) + }, + setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) + options := resources.WriteOptions{ + Path: "grafana", + Ref: "feature/branch", + } + + repoResources.On("WriteResourceFileFromObject", mock.Anything, mock.MatchedBy(func(obj *unstructured.Unstructured) bool { + // Verify that the object has the expected status.conversion.storedVersion field + status, exists, err := unstructured.NestedMap(obj.Object, "status") + if !exists || err != nil { + return false + } + + conversion, exists, err := unstructured.NestedMap(status, "conversion") + if !exists || err != nil { + return false + } + + storedVersion, exists, err := unstructured.NestedString(conversion, "storedVersion") + if !exists || err != nil { + return false + } + + if storedVersion != "v0xyz" { + return false + } + + return obj.GetName() == "existing-dashboard" + }), options).Return("", fmt.Errorf("XXX")) + }, + }, + { + name: "dashboard with failed conversion but no stored version", + mockItems: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "dashboard-no-stored-version", + }, + "status": map[string]interface{}{ + "conversion": map[string]interface{}{ + "failed": true, + // No storedVersion field + }, + }, + }, + }, + }, + expectedError: "", + setupProgress: func(progress *jobs.MockJobProgressRecorder) { + progress.On("SetMessage", mock.Anything, "start resource export").Return() + progress.On("SetMessage", mock.Anything, "export dashboards").Return() + progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + return result.Name == "dashboard-no-stored-version" && + result.Action == repository.FileActionIgnored && + result.Error != nil + })).Return() + progress.On("TooManyErrors").Return(nil) + }, + setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) + // The value is not saved + }, + }, + { + name: "handles v2 dashboard version", + mockItems: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "v2-dashboard", + }, + "status": map[string]interface{}{ + "conversion": map[string]interface{}{ + "failed": true, + "storedVersion": "v2", + }, + }, + }, + }, + }, + expectedError: "", + setupProgress: func(progress *jobs.MockJobProgressRecorder) { + progress.On("SetMessage", mock.Anything, "start resource export").Return() + progress.On("SetMessage", mock.Anything, "export dashboards").Return() + progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + return result.Name == "v2-dashboard" && result.Action == repository.FileActionCreated + })).Return() + progress.On("TooManyErrors").Return(nil) + }, + setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + // Setup v1 client + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) + + // Setup v2 client + + // Mock v2 client Get call + v2Dashboard := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "dashboard.grafana.app/v2alpha1", + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "v2-dashboard", + }, + "spec": map[string]interface{}{ + "version": 2, + "title": "V2 Dashboard", + }, + }, + } + v2Client := &mockDynamicInterface{items: []unstructured.Unstructured{*v2Dashboard}} + resourceClients.On("ForResource", resources.DashboardResourceV2).Return(v2Client, gvk, nil) + + options := resources.WriteOptions{ + Path: "grafana", + Ref: "feature/branch", + } + repoResources.On("WriteResourceFileFromObject", mock.Anything, v2Dashboard, options).Return("v2-dashboard.json", nil) + }, + }, + { + name: "handles v2 client creation error", + mockItems: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "v2-dashboard-error", + }, + "status": map[string]interface{}{ + "conversion": map[string]interface{}{ + "failed": true, + "storedVersion": "v2", + }, + }, + }, + }, + }, + setupProgress: func(progress *jobs.MockJobProgressRecorder) { + progress.On("SetMessage", mock.Anything, "start resource export").Return() + progress.On("SetMessage", mock.Anything, "export dashboards").Return() + progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + if result.Name != "v2-dashboard-error" { + return false + } + if result.Action != repository.FileActionIgnored { + return false + } + if result.Error == nil { + return false + } + + if result.Error.Error() != "v2 client error" { + return false + } + + return true + })).Return() + progress.On("TooManyErrors").Return(nil) + }, + setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) + resourceClients.On("ForResource", resources.DashboardResourceV2).Return(nil, gvk, fmt.Errorf("v2 client error")) + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - scheme := runtime.NewScheme() - require.NoError(t, metav1.AddMetaToScheme(scheme)) - listGVK := schema.GroupVersionKind{ - Group: resources.DashboardResource.Group, - Version: resources.DashboardResource.Version, - Kind: "DashboardList", + mockClient := &mockDynamicInterface{ + items: tt.mockItems, } - scheme.AddKnownTypeWithName(listGVK, &metav1.PartialObjectMetadataList{}) - scheme.AddKnownTypeWithName(schema.GroupVersionKind{ - Group: resources.DashboardResource.Group, - Version: resources.DashboardResource.Version, - Kind: resources.DashboardResource.Resource, - }, &metav1.PartialObjectMetadata{}) - - fakeDynamicClient := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(scheme, map[schema.GroupVersionResource]string{ - resources.DashboardResource: listGVK.Kind, - }) - resourceClients := resources.NewMockResourceClients(t) - fakeDynamicClient.PrependReactor("list", "dashboards", tt.reactorFunc) - mockProgress := jobs.NewMockJobProgressRecorder(t) tt.setupProgress(mockProgress) repoResources := resources.NewMockRepositoryResources(t) - tt.setupResources(repoResources, resourceClients, fakeDynamicClient, listGVK) + tt.setupResources(repoResources, resourceClients, mockClient, schema.GroupVersionKind{ + Group: resources.DashboardResource.Group, + Version: resources.DashboardResource.Version, + Kind: "DashboardList", + }) options := provisioningV0.ExportJobOptions{ Path: "grafana", diff --git a/pkg/registry/apis/provisioning/resources/client.go b/pkg/registry/apis/provisioning/resources/client.go index fcdabcb0705..b27f45b6892 100644 --- a/pkg/registry/apis/provisioning/resources/client.go +++ b/pkg/registry/apis/provisioning/resources/client.go @@ -10,7 +10,8 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" - dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" + dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" + dashboardV2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" iam "github.com/grafana/grafana/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/services/apiserver" @@ -18,9 +19,10 @@ import ( ) var ( - UserResource = iam.UserResourceInfo.GroupVersionResource() - FolderResource = folders.FolderResourceInfo.GroupVersionResource() - DashboardResource = dashboard.DashboardResourceInfo.GroupVersionResource() + UserResource = iam.UserResourceInfo.GroupVersionResource() + FolderResource = folders.FolderResourceInfo.GroupVersionResource() + DashboardResource = dashboardV1.DashboardResourceInfo.GroupVersionResource() + DashboardResourceV2 = dashboardV2.DashboardResourceInfo.GroupVersionResource() // SupportedProvisioningResources is the list of resources that can fully managed from the UI SupportedProvisioningResources = []schema.GroupVersionResource{FolderResource, DashboardResource} diff --git a/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v0.yaml b/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v0.yaml new file mode 100644 index 00000000000..2d4cf82b060 --- /dev/null +++ b/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v0.yaml @@ -0,0 +1,8 @@ +apiVersion: dashboard.grafana.app/v0alpha1 +kind: Dashboard +metadata: + name: test-v0 +spec: + title: Test dashboard. Created at v0 + uid: test-v0 # will be removed by mutation hook + version: 1234567 # will be removed by mutation hook diff --git a/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v1.yaml b/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v1.yaml new file mode 100644 index 00000000000..18dee5c28d9 --- /dev/null +++ b/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v1.yaml @@ -0,0 +1,9 @@ +apiVersion: dashboard.grafana.app/v1beta1 +kind: Dashboard +metadata: + name: test-v1 +spec: + title: Test dashboard. Created at v1 + uid: test-v1 # will be removed by mutation hook + version: 1234567 # will be removed by mutation hook + schemaVersion: 41 diff --git a/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v2.yaml b/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v2.yaml new file mode 100644 index 00000000000..540b9dcc919 --- /dev/null +++ b/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v2.yaml @@ -0,0 +1,10 @@ +apiVersion: dashboard.grafana.app/v2alpha1 +kind: Dashboard +metadata: + name: test-v2 +spec: + title: Test dashboard. Created at v2 + layout: + kind: GridLayout + spec: + items: [] diff --git a/pkg/tests/apis/provisioning/exportunifiedtorepository/root_dashboard.json b/pkg/tests/apis/provisioning/exportunifiedtorepository/root_dashboard.json deleted file mode 100644 index b2008d618b0..00000000000 --- a/pkg/tests/apis/provisioning/exportunifiedtorepository/root_dashboard.json +++ /dev/null @@ -1,162 +0,0 @@ -{ - "apiVersion": "dashboard.grafana.app/v1beta1", - "kind": "Dashboard", - "metadata": { - "name": "root_dashboard", - "namespace": "default" - }, - "spec": { - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "grafana", - "uid": "-- Grafana --" - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "type": "dashboard" - } - ] - }, - "description": "This is on the root level of the repository.", - "editable": true, - "fiscalYearStartMonth": 0, - "graphTooltip": 0, - "links": [], - "panels": [ - { - "datasource": { - "type": "datasource", - "uid": "grafana" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "continuous-BlPu", - "seriesBy": "last" - }, - "custom": { - "axisBorderShow": true, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisGridShow": true, - "axisLabel": "Y axis on the right side", - "axisPlacement": "right", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "points", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 7, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "dashed+area" - } - }, - "decimals": 3, - "fieldMinMax": false, - "links": [ - { - "oneClick": true, - "targetBlank": true, - "title": "Test test", - "url": "https://github.com/grafana/grafana" - } - ], - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "transparent" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "kvoltamp" - }, - "overrides": [] - }, - "gridPos": { - "h": 24, - "w": 22, - "x": 0, - "y": 0 - }, - "id": 1, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "multi", - "sort": "none" - } - }, - "pluginVersion": "11.6.0-pre", - "targets": [ - { - "datasource": { - "type": "datasource", - "uid": "grafana" - }, - "queryType": "randomWalk", - "refId": "A" - } - ], - "title": "Test panel", - "type": "timeseries" - } - ], - "preload": false, - "schemaVersion": 41, - "tags": [ - "grafana", - "dashboard", - "root", - "test" - ], - "templating": { - "list": [] - }, - "time": { - "from": "now-6h", - "to": "now" - }, - "timepicker": {}, - "timezone": "Pacific/Kiritimati", - "title": "Test dashboard on root level", - "uid": "", - "version": 0, - "weekStart": "saturday" - } -} diff --git a/pkg/tests/apis/provisioning/helper_test.go b/pkg/tests/apis/provisioning/helper_test.go index e7226447fd1..bf63e2358fc 100644 --- a/pkg/tests/apis/provisioning/helper_test.go +++ b/pkg/tests/apis/provisioning/helper_test.go @@ -23,7 +23,9 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/rest" - dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" + dashboardV0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" + dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" + dashboardV2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" folder "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" @@ -46,7 +48,9 @@ type provisioningTestHelper struct { Repositories *apis.K8sResourceClient Jobs *apis.K8sResourceClient Folders *apis.K8sResourceClient - Dashboards *apis.K8sResourceClient + DashboardsV0 *apis.K8sResourceClient + DashboardsV1 *apis.K8sResourceClient + DashboardsV2 *apis.K8sResourceClient AdminREST *rest.RESTClient EditorREST *rest.RESTClient ViewerREST *rest.RESTClient @@ -250,10 +254,20 @@ func runGrafana(t *testing.T, options ...grafanaOption) *provisioningTestHelper Namespace: "default", // actually org1 GVR: folder.FolderResourceInfo.GroupVersionResource(), }) - dashboards := helper.GetResourceClient(apis.ResourceClientArgs{ + dashboardsV0 := helper.GetResourceClient(apis.ResourceClientArgs{ User: helper.Org1.Admin, Namespace: "default", // actually org1 - GVR: dashboard.DashboardResourceInfo.GroupVersionResource(), + GVR: dashboardV0.DashboardResourceInfo.GroupVersionResource(), + }) + dashboardsV1 := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + Namespace: "default", // actually org1 + GVR: dashboardV1.DashboardResourceInfo.GroupVersionResource(), + }) + dashboardsV2 := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + Namespace: "default", // actually org1 + GVR: dashboardV2.DashboardResourceInfo.GroupVersionResource(), }) // Repo client, but less guard rails. Useful for subresources. We'll need this later... @@ -276,7 +290,7 @@ func runGrafana(t *testing.T, options ...grafanaOption) *provisioningTestHelper return nil } - require.NoError(t, deleteAll(dashboards), "deleting all dashboards") + require.NoError(t, deleteAll(dashboardsV1), "deleting all dashboards") // v0+v1+v2 require.NoError(t, deleteAll(folders), "deleting all folders") require.NoError(t, deleteAll(repositories), "deleting all repositories") @@ -290,7 +304,9 @@ func runGrafana(t *testing.T, options ...grafanaOption) *provisioningTestHelper ViewerREST: viewerClient, Jobs: jobs, Folders: folders, - Dashboards: dashboards, + DashboardsV0: dashboardsV0, + DashboardsV1: dashboardsV1, + DashboardsV2: dashboardsV2, } } diff --git a/pkg/tests/apis/provisioning/provisioning_test.go b/pkg/tests/apis/provisioning/provisioning_test.go index b70137b823a..22ec10b7073 100644 --- a/pkg/tests/apis/provisioning/provisioning_test.go +++ b/pkg/tests/apis/provisioning/provisioning_test.go @@ -147,7 +147,7 @@ func TestIntegrationProvisioning_FailInvalidSchema(t *testing.T) { require.Equal(t, status.Message, "Dry run failed: Dashboard.dashboard.grafana.app \"invalid-schema-uid\" is invalid: [spec.panels.0.repeatDirection: Invalid value: conflicting values \"h\" and \"this is not an allowed value\", spec.panels.0.repeatDirection: Invalid value: conflicting values \"v\" and \"this is not an allowed value\"]") const invalidSchemaUid = "invalid-schema-uid" - _, err = helper.Dashboards.Resource.Get(ctx, invalidSchemaUid, metav1.GetOptions{}) + _, err = helper.DashboardsV1.Resource.Get(ctx, invalidSchemaUid, metav1.GetOptions{}) require.Error(t, err, "invalid dashboard shouldn't exist") require.True(t, apierrors.IsNotFound(err)) @@ -195,7 +195,7 @@ func TestIntegrationProvisioning_FailInvalidSchema(t *testing.T) { require.Equal(t, job.Status.Errors[0], "Dashboard.dashboard.grafana.app \"invalid-schema-uid\" is invalid: [spec.panels.0.repeatDirection: Invalid value: conflicting values \"h\" and \"this is not an allowed value\", spec.panels.0.repeatDirection: Invalid value: conflicting values \"v\" and \"this is not an allowed value\"]") }, time.Second*10, time.Millisecond*10, "Expected provisioning job to conclude with the status failed") - _, err = helper.Dashboards.Resource.Get(ctx, invalidSchemaUid, metav1.GetOptions{}) + _, err = helper.DashboardsV1.Resource.Get(ctx, invalidSchemaUid, metav1.GetOptions{}) require.Error(t, err, "invalid dashboard shouldn't have been created") require.True(t, apierrors.IsNotFound(err)) @@ -272,7 +272,7 @@ func TestIntegrationProvisioning_CreatingGitHubRepository(t *testing.T) { // By now, we should have synced, meaning we have data to read in the local Grafana instance! - found, err := helper.Dashboards.Resource.List(ctx, metav1.ListOptions{}) + found, err := helper.DashboardsV1.Resource.List(ctx, metav1.ListOptions{}) require.NoError(t, err, "can list values") names := []string{} @@ -286,7 +286,7 @@ func TestIntegrationProvisioning_CreatingGitHubRepository(t *testing.T) { require.NoError(t, err, "should delete values") assert.EventuallyWithT(t, func(collect *assert.CollectT) { - found, err := helper.Dashboards.Resource.List(ctx, metav1.ListOptions{}) + found, err := helper.DashboardsV1.Resource.List(ctx, metav1.ListOptions{}) require.NoError(t, err, "can list values") require.Equal(collect, 0, len(found.Items), "expected dashboards to be deleted") }, time.Second*10, time.Millisecond*10, "Expected dashboards to be deleted") @@ -396,7 +396,7 @@ func TestIntegrationProvisioning_RunLocalRepository(t *testing.T) { require.Equal(t, allPanels, name, "save the name from the request") // Get the file from the grafana database - obj, err := helper.Dashboards.Resource.Get(ctx, allPanels, metav1.GetOptions{}) + obj, err := helper.DashboardsV1.Resource.Get(ctx, allPanels, metav1.GetOptions{}) require.NoError(t, err, "the value should be saved in grafana") val, _, _ := unstructured.NestedString(obj.Object, "metadata", "annotations", utils.AnnoKeyManagerKind) require.Equal(t, string(utils.ManagerKindRepo), val, "should have repo annotations") @@ -539,33 +539,33 @@ func TestIntegrationProvisioning_ImportAllPanelsFromLocalRepository(t *testing.T // But the dashboard shouldn't exist yet const allPanels = "n1jR8vnnz" - _, err = helper.Dashboards.Resource.Get(ctx, allPanels, metav1.GetOptions{}) + _, err = helper.DashboardsV1.Resource.Get(ctx, allPanels, metav1.GetOptions{}) require.Error(t, err, "no all-panels dashboard should exist") require.True(t, apierrors.IsNotFound(err)) // Now, we import it, such that it may exist helper.SyncAndWait(t, repo, nil) - _, err = helper.Dashboards.Resource.List(ctx, metav1.ListOptions{}) + _, err = helper.DashboardsV1.Resource.List(ctx, metav1.ListOptions{}) require.NoError(t, err, "can list values") - obj, err = helper.Dashboards.Resource.Get(ctx, allPanels, metav1.GetOptions{}) + obj, err = helper.DashboardsV1.Resource.Get(ctx, allPanels, metav1.GetOptions{}) require.NoError(t, err, "all-panels dashboard should exist") require.Equal(t, repo, obj.GetAnnotations()[utils.AnnoKeyManagerIdentity]) // Try writing the value directly err = unstructured.SetNestedField(obj.Object, []any{"aaa", "bbb"}, "spec", "tags") require.NoError(t, err, "set tags") - obj, err = helper.Dashboards.Resource.Update(ctx, obj, metav1.UpdateOptions{}) + obj, err = helper.DashboardsV1.Resource.Update(ctx, obj, metav1.UpdateOptions{}) require.NoError(t, err) v, _, _ := unstructured.NestedString(obj.Object, "metadata", "annotations", utils.AnnoKeyUpdatedBy) require.Equal(t, "access-policy:provisioning", v) // Should not be able to directly delete the managed resource - err = helper.Dashboards.Resource.Delete(ctx, allPanels, metav1.DeleteOptions{}) + err = helper.DashboardsV1.Resource.Delete(ctx, allPanels, metav1.DeleteOptions{}) require.NoError(t, err, "user can delete") - _, err = helper.Dashboards.Resource.Get(ctx, allPanels, metav1.GetOptions{}) + _, err = helper.DashboardsV1.Resource.Get(ctx, allPanels, metav1.GetOptions{}) require.Error(t, err, "should delete the internal resource") require.True(t, apierrors.IsNotFound(err)) } @@ -578,10 +578,18 @@ func TestProvisioning_ExportUnifiedToRepository(t *testing.T) { helper := runGrafana(t) ctx := context.Background() - // Set up dashboards first, then the repository, and finally export. - dashboard := helper.LoadYAMLOrJSONFile("exportunifiedtorepository/root_dashboard.json") - _, err := helper.Dashboards.Resource.Create(ctx, dashboard, metav1.CreateOptions{}) - require.NoError(t, err, "should be able to create prerequisite dashboard") + // Write dashboards at + dashboard := helper.LoadYAMLOrJSONFile("exportunifiedtorepository/dashboard-test-v0.yaml") + _, err := helper.DashboardsV0.Resource.Create(ctx, dashboard, metav1.CreateOptions{}) + require.NoError(t, err, "should be able to create v0 dashboard") + + dashboard = helper.LoadYAMLOrJSONFile("exportunifiedtorepository/dashboard-test-v1.yaml") + _, err = helper.DashboardsV1.Resource.Create(ctx, dashboard, metav1.CreateOptions{}) + require.NoError(t, err, "should be able to create v1 dashboard") + + dashboard = helper.LoadYAMLOrJSONFile("exportunifiedtorepository/dashboard-test-v2.yaml") + _, err = helper.DashboardsV2.Resource.Create(ctx, dashboard, metav1.CreateOptions{}) + require.NoError(t, err, "should be able to create v2 dashboard") // Now for the repository. const repo = "local-repository" @@ -608,7 +616,38 @@ func TestProvisioning_ExportUnifiedToRepository(t *testing.T) { // And time to assert. helper.AwaitJobs(t, repo) - fpath := filepath.Join(helper.ProvisioningPath, slugify.Slugify(mustNestedString(dashboard.Object, "spec", "title"))+".json") - _, err = os.Stat(fpath) - require.NoError(t, err, "exported file was not created at path %s", fpath) + type props struct { + title string + apiVersion string + name string + } + + // Check that each file was exported with its stored version + for _, test := range []props{ + {title: "Test dashboard. Created at v0", apiVersion: "dashboard.grafana.app/v0alpha1", name: "test-v0"}, + {title: "Test dashboard. Created at v1", apiVersion: "dashboard.grafana.app/v1beta1", name: "test-v1"}, + {title: "Test dashboard. Created at v2", apiVersion: "dashboard.grafana.app/v2alpha1", name: "test-v2"}, + } { + fpath := filepath.Join(helper.ProvisioningPath, slugify.Slugify(test.title)+".json") + //nolint:gosec // we are ok with reading files in testdata + body, err := os.ReadFile(fpath) + require.NoError(t, err, "exported file was not created at path %s", fpath) + obj := map[string]any{} + err = json.Unmarshal(body, &obj) + require.NoError(t, err, "exported file not json %s", fpath) + + val, _, err := unstructured.NestedString(obj, "apiVersion") + require.NoError(t, err) + require.Equal(t, test.apiVersion, val) + + val, _, err = unstructured.NestedString(obj, "spec", "title") + require.NoError(t, err) + require.Equal(t, test.title, val) + + val, _, err = unstructured.NestedString(obj, "metadata", "name") + require.NoError(t, err) + require.Equal(t, test.name, val) + + require.Nil(t, obj["status"], "should not have a status element") + } } From f0c841b9bc4fa2f4d09e68c7285fb1d83b2c40d0 Mon Sep 17 00:00:00 2001 From: Andres Martinez Gotor Date: Thu, 24 Apr 2025 15:27:13 +0200 Subject: [PATCH 094/146] Advisor: Allow to disable backend (#104445) --- pkg/registry/apps/apps.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/registry/apps/apps.go b/pkg/registry/apps/apps.go index 40e6d8c830b..9de8798d97e 100644 --- a/pkg/registry/apps/apps.go +++ b/pkg/registry/apps/apps.go @@ -2,6 +2,7 @@ package appregistry import ( "context" + "slices" "github.com/grafana/grafana-app-sdk/app" "github.com/grafana/grafana/pkg/infra/log" @@ -13,6 +14,7 @@ import ( "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/apiserver/builder/runner" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/setting" "k8s.io/client-go/rest" ) @@ -33,6 +35,7 @@ func ProvideRegistryServiceSink( playlistAppProvider *playlist.PlaylistAppProvider, investigationAppProvider *investigations.InvestigationsAppProvider, advisorAppProvider *advisor.AdvisorAppProvider, + grafanaCfg *setting.Cfg, ) (*Service, error) { cfgWrapper := func(ctx context.Context) (*rest.Config, error) { cfg, err := restConfigProvider.GetRestConfig(ctx) @@ -55,7 +58,8 @@ func ProvideRegistryServiceSink( logger.Debug("Investigations backend is enabled") providers = append(providers, investigationAppProvider) } - if features.IsEnabledGlobally(featuremgmt.FlagGrafanaAdvisor) { + if features.IsEnabledGlobally(featuremgmt.FlagGrafanaAdvisor) && + !slices.Contains(grafanaCfg.DisablePlugins, "grafana-advisor-app") { providers = append(providers, advisorAppProvider) } apiGroupRunner, err = runner.NewAPIGroupRunner(cfg, providers...) From e7b800f35a2462752b1de28f1473a7b72ca7ab40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agn=C3=A8s=20Toulet?= <35176601+AgnesToulet@users.noreply.github.com> Date: Thu, 24 Apr 2025 15:31:19 +0200 Subject: [PATCH 095/146] Rendering: Add support for rate limiter (#103987) * Rendering: Add support for rate limiter * remove unnecessary golint exception * add lint exception back --- pkg/api/render.go | 5 +++++ pkg/services/rendering/http_mode.go | 6 ++++++ pkg/services/rendering/interface.go | 1 + 3 files changed, 12 insertions(+) diff --git a/pkg/api/render.go b/pkg/api/render.go index 582c6ec1e4b..5db0b88fa35 100644 --- a/pkg/api/render.go +++ b/pkg/api/render.go @@ -98,6 +98,11 @@ func (hs *HTTPServer) RenderHandler(c *contextmodel.ReqContext) { Theme: themeModel, }, nil) if err != nil { + if errors.Is(err, rendering.ErrTooManyRequests) { + c.JsonApiErr(http.StatusTooManyRequests, "Too many rendering requests", err) + return + } + if errors.Is(err, rendering.ErrTimeout) { c.Handle(hs.Cfg, http.StatusInternalServerError, err.Error(), err) return diff --git a/pkg/services/rendering/http_mode.go b/pkg/services/rendering/http_mode.go index 705f9672240..ea15eb389e5 100644 --- a/pkg/services/rendering/http_mode.go +++ b/pkg/services/rendering/http_mode.go @@ -31,6 +31,7 @@ var netClient = &http.Client{ } const authTokenHeader = "X-Auth-Token" //#nosec G101 -- This is a false positive +const rateLimiterHeader = "X-Tenant-ID" var ( remoteVersionFetchInterval time.Duration = time.Second * 15 @@ -160,6 +161,7 @@ func (rs *RenderingService) doRequest(ctx context.Context, u *url.URL, headers m } req.Header.Set(authTokenHeader, rs.Cfg.RendererAuthToken) + req.Header.Set(rateLimiterHeader, rs.domain) req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", rs.Cfg.BuildVersion)) for k, v := range headers { req.Header[k] = v @@ -180,6 +182,10 @@ func (rs *RenderingService) doRequest(ctx context.Context, u *url.URL, headers m return nil, fmt.Errorf("failed to send request to remote rendering service: %w", err) } + if resp.StatusCode == http.StatusTooManyRequests { + return nil, ErrTooManyRequests + } + return resp, nil } diff --git a/pkg/services/rendering/interface.go b/pkg/services/rendering/interface.go index 9b5b2f17cdc..a54d2b92560 100644 --- a/pkg/services/rendering/interface.go +++ b/pkg/services/rendering/interface.go @@ -14,6 +14,7 @@ var ErrTimeout = errors.New("timeout error - you can set timeout in seconds with var ErrConcurrentLimitReached = errors.New("rendering concurrent limit reached") var ErrRenderUnavailable = errors.New("rendering plugin not available") var ErrServerTimeout = errutil.NewBase(errutil.StatusUnknown, "rendering.serverTimeout", errutil.WithPublicMessage("error trying to connect to image-renderer service")) +var ErrTooManyRequests = errutil.NewBase(errutil.StatusTooManyRequests, "rendering.tooManyRequests", errutil.WithPublicMessage("trying to send too many requests to image-renderer service")) type RenderType string From 08205d64d1af8383932b11af100049d166922f4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20=C5=A0tibran=C3=BD?= Date: Thu, 24 Apr 2025 16:10:03 +0200 Subject: [PATCH 096/146] libraryelements: Spanner compability (#103881) * libraryelements: fix compatibility with Spanner * Log errors leading to status code 500. * Rename tests to make them integration tests. --- pkg/services/libraryelements/api.go | 22 ++++++++++--------- .../libraryelements_create_test.go | 2 +- .../libraryelements_delete_test.go | 5 +++-- .../libraryelements_get_all_test.go | 8 ++++--- .../libraryelements_get_test.go | 5 +++-- .../libraryelements_patch_test.go | 5 +++-- .../libraryelements/libraryelements_test.go | 5 +++-- pkg/services/libraryelements/writers.go | 9 +++++--- 8 files changed, 36 insertions(+), 25 deletions(-) diff --git a/pkg/services/libraryelements/api.go b/pkg/services/libraryelements/api.go index 09efc428c11..a26336e63c1 100644 --- a/pkg/services/libraryelements/api.go +++ b/pkg/services/libraryelements/api.go @@ -81,7 +81,7 @@ func (l *LibraryElementService) createHandler(c *contextmodel.ReqContext) respon element, err := l.createLibraryElement(c.Req.Context(), c.SignedInUser, cmd) if err != nil { - return toLibraryElementError(err, "Failed to create library element") + return l.toLibraryElementError(err, "Failed to create library element") } metrics.MFolderIDsServiceCount.WithLabelValues(metrics.LibraryElements).Inc() @@ -118,7 +118,7 @@ func (l *LibraryElementService) createHandler(c *contextmodel.ReqContext) respon func (l *LibraryElementService) deleteHandler(c *contextmodel.ReqContext) response.Response { id, err := l.deleteLibraryElement(c.Req.Context(), c.SignedInUser, web.Params(c.Req)[":uid"]) if err != nil { - return toLibraryElementError(err, "Failed to delete library element") + return l.toLibraryElementError(err, "Failed to delete library element") } return response.JSON(http.StatusOK, model.DeleteLibraryElementResponse{ @@ -148,7 +148,7 @@ func (l *LibraryElementService) getHandler(c *contextmodel.ReqContext) response. }, ) if err != nil { - return toLibraryElementError(err, "Failed to get library element") + return l.toLibraryElementError(err, "Failed to get library element") } if l.features.IsEnabled(ctx, featuremgmt.FlagLibraryPanelRBAC) { @@ -189,13 +189,13 @@ func (l *LibraryElementService) getAllHandler(c *contextmodel.ReqContext) respon } elementsResult, err := l.getAllLibraryElements(c.Req.Context(), c.SignedInUser, query) if err != nil { - return toLibraryElementError(err, "Failed to get library elements") + return l.toLibraryElementError(err, "Failed to get library elements") } if l.features.IsEnabled(c.Req.Context(), featuremgmt.FlagLibraryPanelRBAC) { filteredPanels, err := l.filterLibraryPanelsByPermission(c, elementsResult.Elements) if err != nil { - return toLibraryElementError(err, "Failed to evaluate permissions") + return l.toLibraryElementError(err, "Failed to evaluate permissions") } elementsResult.Elements = filteredPanels } @@ -241,7 +241,7 @@ func (l *LibraryElementService) patchHandler(c *contextmodel.ReqContext) respons element, err := l.patchLibraryElement(c.Req.Context(), c.SignedInUser, cmd, web.Params(c.Req)[":uid"]) if err != nil { - return toLibraryElementError(err, "Failed to update library element") + return l.toLibraryElementError(err, "Failed to update library element") } metrics.MFolderIDsServiceCount.WithLabelValues(metrics.LibraryElements).Inc() @@ -276,7 +276,7 @@ func (l *LibraryElementService) patchHandler(c *contextmodel.ReqContext) respons func (l *LibraryElementService) getConnectionsHandler(c *contextmodel.ReqContext) response.Response { connections, err := l.getConnections(c.Req.Context(), c.SignedInUser, web.Params(c.Req)[":uid"]) if err != nil { - return toLibraryElementError(err, "Failed to get connections") + return l.toLibraryElementError(err, "Failed to get connections") } return response.JSON(http.StatusOK, model.LibraryElementConnectionsResponse{Result: connections}) @@ -296,13 +296,13 @@ func (l *LibraryElementService) getConnectionsHandler(c *contextmodel.ReqContext func (l *LibraryElementService) getByNameHandler(c *contextmodel.ReqContext) response.Response { elements, err := l.getLibraryElementsByName(c.Req.Context(), c.SignedInUser, web.Params(c.Req)[":name"]) if err != nil { - return toLibraryElementError(err, "Failed to get library element") + return l.toLibraryElementError(err, "Failed to get library element") } if l.features.IsEnabled(c.Req.Context(), featuremgmt.FlagLibraryPanelRBAC) { filteredElements, err := l.filterLibraryPanelsByPermission(c, elements) if err != nil { - return toLibraryElementError(err, err.Error()) + return l.toLibraryElementError(err, err.Error()) } return response.JSON(http.StatusOK, model.LibraryElementArrayResponse{Result: filteredElements}) @@ -326,7 +326,7 @@ func (l *LibraryElementService) filterLibraryPanelsByPermission(c *contextmodel. return filteredPanels, nil } -func toLibraryElementError(err error, message string) response.Response { +func (l *LibraryElementService) toLibraryElementError(err error, message string) response.Response { if errors.Is(err, model.ErrLibraryElementAlreadyExists) { return response.Error(http.StatusBadRequest, model.ErrLibraryElementAlreadyExists.Error(), err) } @@ -354,6 +354,8 @@ func toLibraryElementError(err error, message string) response.Response { if errors.Is(err, model.ErrLibraryElementUIDTooLong) { return response.Error(http.StatusBadRequest, model.ErrLibraryElementUIDTooLong.Error(), err) } + // Log errors that cause internal server error status code. + l.log.Error(message, "error", err) return response.ErrOrFallback(http.StatusInternalServerError, message, err) } diff --git a/pkg/services/libraryelements/libraryelements_create_test.go b/pkg/services/libraryelements/libraryelements_create_test.go index 45e7a797b5b..5a66d170e83 100644 --- a/pkg/services/libraryelements/libraryelements_create_test.go +++ b/pkg/services/libraryelements/libraryelements_create_test.go @@ -11,7 +11,7 @@ import ( "github.com/grafana/grafana/pkg/util" ) -func TestCreateLibraryElement(t *testing.T) { +func TestIntegration_CreateLibraryElement(t *testing.T) { scenarioWithPanel(t, "When an admin tries to create a library panel that already exists, it should fail", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck diff --git a/pkg/services/libraryelements/libraryelements_delete_test.go b/pkg/services/libraryelements/libraryelements_delete_test.go index 527cc1bd37d..5605313b4b6 100644 --- a/pkg/services/libraryelements/libraryelements_delete_test.go +++ b/pkg/services/libraryelements/libraryelements_delete_test.go @@ -4,15 +4,16 @@ import ( "encoding/json" "testing" + "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/libraryelements/model" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/web" - "github.com/stretchr/testify/require" ) -func TestDeleteLibraryElement(t *testing.T) { +func TestIntegration_DeleteLibraryElement(t *testing.T) { scenarioWithPanel(t, "When an admin tries to delete a library panel that does not exist, it should fail", func(t *testing.T, sc scenarioContext) { resp := sc.service.deleteHandler(sc.reqContext) diff --git a/pkg/services/libraryelements/libraryelements_get_all_test.go b/pkg/services/libraryelements/libraryelements_get_all_test.go index 501696c4791..5b57eac9de9 100644 --- a/pkg/services/libraryelements/libraryelements_get_all_test.go +++ b/pkg/services/libraryelements/libraryelements_get_all_test.go @@ -14,7 +14,7 @@ import ( "github.com/grafana/grafana/pkg/services/search/sort" ) -func TestGetAllLibraryElements(t *testing.T) { +func TestIntegration_GetAllLibraryElements(t *testing.T) { testScenario(t, "When an admin tries to get all library panels and none exists, it should return none", func(t *testing.T, sc scenarioContext) { resp := sc.service.getAllHandler(sc.reqContext) @@ -964,13 +964,14 @@ func TestGetAllLibraryElements(t *testing.T) { require.NoError(t, err) sc.reqContext.Req.Form.Add("perPage", "1") sc.reqContext.Req.Form.Add("page", "1") - sc.reqContext.Req.Form.Add("searchString", "description") + sc.reqContext.Req.Form.Add("searchString", "DeScRiPtIoN") // mixed case to test case-insensitive search. resp = sc.service.getAllHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) var result libraryElementsSearch err = json.Unmarshal(resp.Body(), &result) require.NoError(t, err) + require.Equal(t, int64(1), result.Result.TotalCount) var expected = libraryElementsSearch{ Result: libraryElementsSearchResult{ TotalCount: 1, @@ -1039,13 +1040,14 @@ func TestGetAllLibraryElements(t *testing.T) { err := sc.reqContext.Req.ParseForm() require.NoError(t, err) - sc.reqContext.Req.Form.Add("searchString", "Library Panel") + sc.reqContext.Req.Form.Add("searchString", "library PANEL") // mixed-case to test case-insensitive search. resp = sc.service.getAllHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) var result libraryElementsSearch err = json.Unmarshal(resp.Body(), &result) require.NoError(t, err) + require.Equal(t, int64(2), result.Result.TotalCount) var expected = libraryElementsSearch{ Result: libraryElementsSearchResult{ TotalCount: 2, diff --git a/pkg/services/libraryelements/libraryelements_get_test.go b/pkg/services/libraryelements/libraryelements_get_test.go index 5820e2bded7..42827e3f13a 100644 --- a/pkg/services/libraryelements/libraryelements_get_test.go +++ b/pkg/services/libraryelements/libraryelements_get_test.go @@ -5,6 +5,8 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/kinds/librarypanel" @@ -13,10 +15,9 @@ import ( "github.com/grafana/grafana/pkg/services/libraryelements/model" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/web" - "github.com/stretchr/testify/require" ) -func TestGetLibraryElement(t *testing.T) { +func TestIntegration_GetLibraryElement(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get a library panel that does not exist, it should fail", func(t *testing.T, sc scenarioContext) { // by uid diff --git a/pkg/services/libraryelements/libraryelements_patch_test.go b/pkg/services/libraryelements/libraryelements_patch_test.go index 19cdb8c248a..3a7c962722f 100644 --- a/pkg/services/libraryelements/libraryelements_patch_test.go +++ b/pkg/services/libraryelements/libraryelements_patch_test.go @@ -8,11 +8,12 @@ import ( "github.com/grafana/grafana/pkg/util" "github.com/google/go-cmp/cmp" - "github.com/grafana/grafana/pkg/web" "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/web" ) -func TestPatchLibraryElement(t *testing.T) { +func TestIntegration_PatchLibraryElement(t *testing.T) { scenarioWithPanel(t, "When an admin tries to patch a library panel that does not exist, it should fail", func(t *testing.T, sc scenarioContext) { cmd := model.PatchLibraryElementCommand{Kind: int64(model.PanelElement), Version: 1} diff --git a/pkg/services/libraryelements/libraryelements_test.go b/pkg/services/libraryelements/libraryelements_test.go index 2d99b5edad1..faffe25225e 100644 --- a/pkg/services/libraryelements/libraryelements_test.go +++ b/pkg/services/libraryelements/libraryelements_test.go @@ -62,7 +62,7 @@ func TestMain(m *testing.M) { testsuite.Run(m) } -func TestDeleteLibraryPanelsInFolder(t *testing.T) { +func TestIntegration_DeleteLibraryPanelsInFolder(t *testing.T) { scenarioWithPanel(t, "When an admin tries to delete a folder that contains connected library elements, it should fail", func(t *testing.T, sc scenarioContext) { dashJSON := map[string]any{ @@ -137,7 +137,7 @@ func TestDeleteLibraryPanelsInFolder(t *testing.T) { }) } -func TestGetLibraryPanelConnections(t *testing.T) { +func TestIntegration_GetLibraryPanelConnections(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get connections of library panel, it should succeed and return correct result", func(t *testing.T, sc scenarioContext) { dashJSON := map[string]any{ @@ -549,6 +549,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo SQLStore: sqlStore, folderService: folderSvc, dashboardsService: dashService, + log: log.NewNopLogger(), } // deliberate difference between signed in user and user in db to make it crystal clear diff --git a/pkg/services/libraryelements/writers.go b/pkg/services/libraryelements/writers.go index 9629b96b66a..2c83ebbf4f6 100644 --- a/pkg/services/libraryelements/writers.go +++ b/pkg/services/libraryelements/writers.go @@ -60,8 +60,11 @@ func writeTypeFilterSQL(typeFilter []string, builder *db.SQLBuilder) { func writeSearchStringSQL(query model.SearchLibraryElementsQuery, sqlStore db.DB, builder *db.SQLBuilder) { if len(strings.TrimSpace(query.SearchString)) > 0 { - builder.Write(" AND (le.name "+sqlStore.GetDialect().LikeStr()+" ?", "%"+query.SearchString+"%") - builder.Write(" OR le.description "+sqlStore.GetDialect().LikeStr()+" ?)", "%"+query.SearchString+"%") + sql, param := sqlStore.GetDialect().LikeOperator("le.name", true, query.SearchString, true) + builder.Write(" AND ("+sql, param) + + sql, param = sqlStore.GetDialect().LikeOperator("le.description", true, query.SearchString, true) + builder.Write(" OR "+sql+")", param) } } @@ -147,7 +150,7 @@ func (f *FolderFilter) writeFolderFilterSQL(includeGeneral bool, builder *db.SQL if !includeGeneral && isGeneralFolder(folderID) { continue } - params = append(params, filter) + params = append(params, folderID) } if len(params) > 0 { sql.WriteString(` AND le.folder_id IN (?` + strings.Repeat(",?", len(params)-1) + ")") From 20b50e5f16db5d9372ce4bb76db079f6de14df2d Mon Sep 17 00:00:00 2001 From: Juan Cabanas Date: Thu, 24 Apr 2025 11:27:04 -0300 Subject: [PATCH 097/146] Grafana UI: Make `DashboardPicker` focusable (#104242) --- .../src/components/Select/SelectBase.tsx | 7 +- .../grafana-ui/src/components/Select/types.ts | 2 + .../components/Select/DashboardPicker.tsx | 117 +++++++++--------- 3 files changed, 64 insertions(+), 62 deletions(-) diff --git a/packages/grafana-ui/src/components/Select/SelectBase.tsx b/packages/grafana-ui/src/components/Select/SelectBase.tsx index 899a9a460d8..c9fd5253441 100644 --- a/packages/grafana-ui/src/components/Select/SelectBase.tsx +++ b/packages/grafana-ui/src/components/Select/SelectBase.tsx @@ -1,5 +1,5 @@ import { isArray, negate } from 'lodash'; -import { ComponentProps, useCallback, useEffect, useRef, useState } from 'react'; +import { ComponentProps, useCallback, useEffect, useRef, useState, useImperativeHandle } from 'react'; import * as React from 'react'; import { default as ReactSelect, @@ -152,16 +152,19 @@ export function SelectBase({ isValidNewOption, formatOptionLabel, hideSelectedOptions, + selectRef, ...rest }: SelectBaseProps & Rest) { const theme = useTheme2(); const styles = getSelectStyles(theme); - const reactSelectRef = useRef<{ controlRef: HTMLElement }>(null); + const reactSelectRef = useRef(null); const [closeToBottom, setCloseToBottom] = useState(false); const selectStyles = useCustomSelectStyles(theme, width); const [hasInputValue, setHasInputValue] = useState(!!inputValue); + useImperativeHandle(selectRef, () => reactSelectRef.current!, []); + // Infer the menu position for asynchronously loaded options. menuPlacement="auto" doesn't work when the menu is // automatically opened when the component is created (it happens in SegmentSelect by setting menuIsOpen={true}). // We can remove this workaround when the bug in react-select is fixed: https://github.com/JedWatson/react-select/issues/4936 diff --git a/packages/grafana-ui/src/components/Select/types.ts b/packages/grafana-ui/src/components/Select/types.ts index b5183d97ea3..e31aee53d5a 100644 --- a/packages/grafana-ui/src/components/Select/types.ts +++ b/packages/grafana-ui/src/components/Select/types.ts @@ -116,6 +116,8 @@ export interface SelectCommonProps { loadingMessage?: string; /** Disables wrapping of multi value values when closed */ noMultiValueWrap?: boolean; + /** Use a custom ref because generic component as output of React.forwardRef is not directly possible */ + selectRef?: React.Ref; } export interface SelectAsyncProps { diff --git a/public/app/core/components/Select/DashboardPicker.tsx b/public/app/core/components/Select/DashboardPicker.tsx index efe594ad208..dfbb2458ade 100644 --- a/public/app/core/components/Select/DashboardPicker.tsx +++ b/public/app/core/components/Select/DashboardPicker.tsx @@ -1,5 +1,5 @@ import debounce from 'debounce-promise'; -import { useCallback, useEffect, useState } from 'react'; +import { forwardRef, useCallback, useEffect, useState } from 'react'; import { SelectableValue } from '@grafana/data'; import { AsyncSelectProps, AsyncSelect } from '@grafana/ui'; @@ -38,72 +38,69 @@ async function findDashboards(query = '') { const getDashboards = debounce(findDashboards, 250, { leading: true }); // TODO: this component should provide a way to apply different filters to the search APIs -export const DashboardPicker = ({ - value, - onChange, - placeholder = 'Select dashboard', - noOptionsMessage = 'No dashboards found', - ...props -}: Props) => { - const [current, setCurrent] = useState>(); +export const DashboardPicker = forwardRef( + ({ value, onChange, placeholder = 'Select dashboard', noOptionsMessage = 'No dashboards found', ...props }, ref) => { + const [current, setCurrent] = useState>(); - // This is required because the async select does not match the raw uid value - // We can not use a simple Select because the dashboard search should not return *everything* - useEffect(() => { - if (!value || value === current?.value?.uid) { - return; - } + // This is required because the async select does not match the raw uid value + // We can not use a simple Select because the dashboard search should not return *everything* + useEffect(() => { + if (!value || value === current?.value?.uid) { + return; + } - (async () => { - // value was manually changed from outside or we are rendering for the first time. - // We need to fetch dashboard information. - const dto = await getDashboardAPI().getDashboardDTO(value, undefined); + (async () => { + // value was manually changed from outside or we are rendering for the first time. + // We need to fetch dashboard information. + const dto = await getDashboardAPI().getDashboardDTO(value, undefined); - if (isDashboardV2Resource(dto)) { - setCurrent({ - value: { - uid: dto.metadata.name, - title: dto.spec.title, - folderTitle: dto.metadata.annotations?.[AnnoKeyFolderTitle], - folderUid: dto.metadata.annotations?.[AnnoKeyFolder], - }, - label: formatLabel(dto.metadata.annotations?.[AnnoKeyFolder], dto.spec.title), - }); - } else { - if (dto.dashboard) { + if (isDashboardV2Resource(dto)) { setCurrent({ value: { - uid: dto.dashboard.uid, - title: dto.dashboard.title, - folderTitle: dto.meta.folderTitle, - folderUid: dto.meta.folderUid, + uid: dto.metadata.name, + title: dto.spec.title, + folderTitle: dto.metadata.annotations?.[AnnoKeyFolderTitle], + folderUid: dto.metadata.annotations?.[AnnoKeyFolder], }, - label: formatLabel(dto.meta?.folderTitle, dto.dashboard.title), + label: formatLabel(dto.metadata.annotations?.[AnnoKeyFolder], dto.spec.title), }); + } else { + if (dto.dashboard) { + setCurrent({ + value: { + uid: dto.dashboard.uid, + title: dto.dashboard.title, + folderTitle: dto.meta.folderTitle, + folderUid: dto.meta.folderUid, + }, + label: formatLabel(dto.meta?.folderTitle, dto.dashboard.title), + }); + } } - } - })(); - // we don't need to rerun this effect every time `current` changes - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [value]); + })(); + // we don't need to rerun this effect every time `current` changes + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [value]); - const onPicked = useCallback( - (sel: SelectableValue) => { - setCurrent(sel); - onChange?.(sel?.value); - }, - [onChange, setCurrent] - ); + const onPicked = useCallback( + (sel: SelectableValue) => { + setCurrent(sel); + onChange?.(sel?.value); + }, + [onChange, setCurrent] + ); - return ( - - ); -}; + return ( + + ); + } +); From f7216db6bcf18b9c65f3c5bd0564c9666880ff91 Mon Sep 17 00:00:00 2001 From: Kevin Minehart <5140827+kminehart@users.noreply.github.com> Date: Thu, 24 Apr 2025 09:47:14 -0500 Subject: [PATCH 098/146] Chore: bump version to 12.1.0-pre (#104468) bump version to 12.1.0-pre --- .../grafana-extensionstest-app/package.json | 2 +- .../grafana-test-datasource/package.json | 2 +- lerna.json | 2 +- package.json | 2 +- packages/grafana-data/package.json | 4 +- packages/grafana-e2e-selectors/package.json | 2 +- packages/grafana-eslint-rules/package.json | 2 +- packages/grafana-flamegraph/package.json | 6 +- .../grafana-o11y-ds-frontend/package.json | 12 +- packages/grafana-plugin-configs/package.json | 2 +- packages/grafana-prometheus/package.json | 12 +- packages/grafana-runtime/package.json | 10 +- packages/grafana-schema/package.json | 2 +- .../x/AnnotationsListPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/BarChartPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/BarGaugePanelCfg_types.gen.ts | 2 +- .../x/CandlestickPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/CanvasPanelCfg_types.gen.ts | 2 +- .../x/CloudWatchDataQuery_types.gen.ts | 2 +- .../x/DashboardListPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/DatagridPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/DebugPanelCfg_types.gen.ts | 2 +- .../x/ElasticsearchDataQuery_types.gen.ts | 2 +- .../panelcfg/x/GaugePanelCfg_types.gen.ts | 2 +- .../panelcfg/x/GeomapPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/HeatmapPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/HistogramPanelCfg_types.gen.ts | 2 +- .../logs/panelcfg/x/LogsPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/LogsNewPanelCfg_types.gen.ts | 2 +- .../dataquery/x/LokiDataQuery_types.gen.ts | 2 +- .../news/panelcfg/x/NewsPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/NodeGraphPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/PieChartPanelCfg_types.gen.ts | 2 +- .../stat/panelcfg/x/StatPanelCfg_types.gen.ts | 2 +- .../x/StateTimelinePanelCfg_types.gen.ts | 2 +- .../x/StatusHistoryPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/TablePanelCfg_types.gen.ts | 2 +- .../text/panelcfg/x/TextPanelCfg_types.gen.ts | 2 +- .../x/TimeSeriesPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/TrendPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/XYChartPanelCfg_types.gen.ts | 2 +- packages/grafana-sql/package.json | 10 +- packages/grafana-ui/package.json | 8 +- .../datasource/azuremonitor/package.json | 14 +- .../datasource/cloud-monitoring/package.json | 14 +- .../package.json | 14 +- .../grafana-pyroscope-datasource/package.json | 12 +- .../grafana-testdata-datasource/package.json | 14 +- .../plugins/datasource/jaeger/package.json | 2 +- .../app/plugins/datasource/mssql/package.json | 14 +- .../app/plugins/datasource/mysql/package.json | 14 +- .../app/plugins/datasource/parca/package.json | 12 +- .../app/plugins/datasource/tempo/package.json | 4 +- .../plugins/datasource/zipkin/package.json | 2 +- yarn.lock | 156 +++++++++--------- 55 files changed, 203 insertions(+), 203 deletions(-) diff --git a/e2e/test-plugins/grafana-extensionstest-app/package.json b/e2e/test-plugins/grafana-extensionstest-app/package.json index d5f05088d35..73e3b952bc8 100644 --- a/e2e/test-plugins/grafana-extensionstest-app/package.json +++ b/e2e/test-plugins/grafana-extensionstest-app/package.json @@ -1,6 +1,6 @@ { "name": "@test-plugins/extensions-test-app", - "version": "12.0.0-pre", + "version": "12.1.0-pre", "private": true, "scripts": { "build": "webpack -c ./webpack.config.ts --env production", diff --git a/e2e/test-plugins/grafana-test-datasource/package.json b/e2e/test-plugins/grafana-test-datasource/package.json index fce57ed8757..bfac0051d93 100644 --- a/e2e/test-plugins/grafana-test-datasource/package.json +++ b/e2e/test-plugins/grafana-test-datasource/package.json @@ -1,6 +1,6 @@ { "name": "@test-plugins/grafana-e2etest-datasource", - "version": "12.0.0-pre", + "version": "12.1.0-pre", "private": true, "scripts": { "build": "webpack -c ./webpack.config.ts --env production", diff --git a/lerna.json b/lerna.json index e0645ae04bc..71b3ceeef8f 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", "npmClient": "yarn", - "version": "12.0.0-pre" + "version": "12.1.0-pre" } diff --git a/package.json b/package.json index a117c9a4e47..264ca1c8d5b 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "license": "AGPL-3.0-only", "private": true, "name": "grafana", - "version": "12.0.0-pre", + "version": "12.1.0-pre", "repository": "github:grafana/grafana", "scripts": { "build": "NODE_ENV=production nx exec --verbose -- webpack --config scripts/webpack/webpack.prod.js --progress", diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index 9713301dd87..5b4380cc03c 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/data", - "version": "12.0.0-pre", + "version": "12.1.0-pre", "description": "Grafana Data Library", "keywords": [ "typescript" @@ -56,7 +56,7 @@ }, "dependencies": { "@braintree/sanitize-url": "7.0.1", - "@grafana/schema": "12.0.0-pre", + "@grafana/schema": "12.1.0-pre", "@types/d3-interpolate": "^3.0.0", "@types/string-hash": "1.1.3", "@types/systemjs": "6.15.1", diff --git a/packages/grafana-e2e-selectors/package.json b/packages/grafana-e2e-selectors/package.json index 7525eb21469..3a81ff7a3aa 100644 --- a/packages/grafana-e2e-selectors/package.json +++ b/packages/grafana-e2e-selectors/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/e2e-selectors", - "version": "12.0.0-pre", + "version": "12.1.0-pre", "description": "Grafana End-to-End Test Selectors Library", "keywords": [ "cli", diff --git a/packages/grafana-eslint-rules/package.json b/packages/grafana-eslint-rules/package.json index 449c41eff0f..58f95284f26 100644 --- a/packages/grafana-eslint-rules/package.json +++ b/packages/grafana-eslint-rules/package.json @@ -1,7 +1,7 @@ { "name": "@grafana/eslint-plugin", "description": "ESLint rules for use within the Grafana repo. Not suitable (or supported) for external use.", - "version": "12.0.0-pre", + "version": "12.1.0-pre", "main": "./index.cjs", "author": "Grafana Labs", "license": "Apache-2.0", diff --git a/packages/grafana-flamegraph/package.json b/packages/grafana-flamegraph/package.json index fdd181fa8f6..f84432883d1 100644 --- a/packages/grafana-flamegraph/package.json +++ b/packages/grafana-flamegraph/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/flamegraph", - "version": "12.0.0-pre", + "version": "12.1.0-pre", "description": "Grafana flamegraph visualization component", "keywords": [ "grafana", @@ -44,8 +44,8 @@ ], "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "@leeoniya/ufuzzy": "1.0.18", "d3": "^7.8.5", "lodash": "4.17.21", diff --git a/packages/grafana-o11y-ds-frontend/package.json b/packages/grafana-o11y-ds-frontend/package.json index 62ab018c940..05b7ef3c8c0 100644 --- a/packages/grafana-o11y-ds-frontend/package.json +++ b/packages/grafana-o11y-ds-frontend/package.json @@ -3,7 +3,7 @@ "license": "AGPL-3.0-only", "name": "@grafana/o11y-ds-frontend", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "description": "Library to manage traces in Grafana.", "sideEffects": false, "repository": { @@ -18,12 +18,12 @@ }, "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.0.0-pre", - "@grafana/e2e-selectors": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", + "@grafana/e2e-selectors": "12.1.0-pre", "@grafana/plugin-ui": "0.10.5", - "@grafana/runtime": "12.0.0-pre", - "@grafana/schema": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/runtime": "12.1.0-pre", + "@grafana/schema": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "react-select": "5.10.0", "react-use": "17.6.0", "rxjs": "7.8.1", diff --git a/packages/grafana-plugin-configs/package.json b/packages/grafana-plugin-configs/package.json index d88cf2ea261..b60ee20e30d 100644 --- a/packages/grafana-plugin-configs/package.json +++ b/packages/grafana-plugin-configs/package.json @@ -2,7 +2,7 @@ "name": "@grafana/plugin-configs", "description": "Shared dependencies and files for core plugins", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "dependencies": { "tslib": "2.8.1" }, diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index a972bef78c0..c5d72898f7d 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "AGPL-3.0-only", "name": "@grafana/prometheus", - "version": "12.0.0-pre", + "version": "12.1.0-pre", "description": "Grafana Prometheus Library", "keywords": [ "typescript" @@ -38,12 +38,12 @@ "dependencies": { "@emotion/css": "11.13.5", "@floating-ui/react": "0.27.7", - "@grafana/data": "12.0.0-pre", - "@grafana/e2e-selectors": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", + "@grafana/e2e-selectors": "12.1.0-pre", "@grafana/plugin-ui": "0.10.5", - "@grafana/runtime": "12.0.0-pre", - "@grafana/schema": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/runtime": "12.1.0-pre", + "@grafana/schema": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "@hello-pangea/dnd": "17.0.0", "@leeoniya/ufuzzy": "1.0.18", "@lezer/common": "1.2.3", diff --git a/packages/grafana-runtime/package.json b/packages/grafana-runtime/package.json index fe80ca32676..baabb6b85d6 100644 --- a/packages/grafana-runtime/package.json +++ b/packages/grafana-runtime/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/runtime", - "version": "12.0.0-pre", + "version": "12.1.0-pre", "description": "Grafana Runtime Library", "keywords": [ "grafana", @@ -53,11 +53,11 @@ "postpack": "mv package.json.bak package.json && rimraf ./unstable" }, "dependencies": { - "@grafana/data": "12.0.0-pre", - "@grafana/e2e-selectors": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", + "@grafana/e2e-selectors": "12.1.0-pre", "@grafana/faro-web-sdk": "^1.13.2", - "@grafana/schema": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/schema": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "@types/systemjs": "6.15.1", "history": "4.10.1", "lodash": "4.17.21", diff --git a/packages/grafana-schema/package.json b/packages/grafana-schema/package.json index d6b8759e8b1..f922daf7569 100644 --- a/packages/grafana-schema/package.json +++ b/packages/grafana-schema/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/schema", - "version": "12.0.0-pre", + "version": "12.1.0-pre", "description": "Grafana Schema Library", "keywords": [ "typescript" diff --git a/packages/grafana-schema/src/raw/composable/annotationslist/panelcfg/x/AnnotationsListPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/annotationslist/panelcfg/x/AnnotationsListPanelCfg_types.gen.ts index b8c475c5cb0..6d79fbeceb8 100644 --- a/packages/grafana-schema/src/raw/composable/annotationslist/panelcfg/x/AnnotationsListPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/annotationslist/panelcfg/x/AnnotationsListPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options { limit: number; diff --git a/packages/grafana-schema/src/raw/composable/barchart/panelcfg/x/BarChartPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/barchart/panelcfg/x/BarChartPanelCfg_types.gen.ts index a613949f576..1bc5ff0f497 100644 --- a/packages/grafana-schema/src/raw/composable/barchart/panelcfg/x/BarChartPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/barchart/panelcfg/x/BarChartPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options extends common.OptionsWithLegend, common.OptionsWithTooltip, common.OptionsWithTextFormatting { /** diff --git a/packages/grafana-schema/src/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen.ts index 8ea837b198b..4345b7fae05 100644 --- a/packages/grafana-schema/src/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options extends common.OptionsWithLegend, common.SingleStatBaseOptions { displayMode: common.BarGaugeDisplayMode; diff --git a/packages/grafana-schema/src/raw/composable/candlestick/panelcfg/x/CandlestickPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/candlestick/panelcfg/x/CandlestickPanelCfg_types.gen.ts index 1ab1eeb4005..ee96c69c0a9 100644 --- a/packages/grafana-schema/src/raw/composable/candlestick/panelcfg/x/CandlestickPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/candlestick/panelcfg/x/CandlestickPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export enum VizDisplayMode { Candles = 'candles', diff --git a/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts index 2e70c51ccbb..84c0c84408d 100644 --- a/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export enum HorizontalConstraint { Center = 'center', diff --git a/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts b/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts index 30ac8aa2af5..9f339bc8cc4 100644 --- a/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface MetricStat { /** diff --git a/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts index c345f24064b..328429a5829 100644 --- a/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options { /** diff --git a/packages/grafana-schema/src/raw/composable/datagrid/panelcfg/x/DatagridPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/datagrid/panelcfg/x/DatagridPanelCfg_types.gen.ts index 14c4faac257..99b9e516dba 100644 --- a/packages/grafana-schema/src/raw/composable/datagrid/panelcfg/x/DatagridPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/datagrid/panelcfg/x/DatagridPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options { selectedSeries: number; diff --git a/packages/grafana-schema/src/raw/composable/debug/panelcfg/x/DebugPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/debug/panelcfg/x/DebugPanelCfg_types.gen.ts index 7ea1d48bd5d..6967fd64ccb 100644 --- a/packages/grafana-schema/src/raw/composable/debug/panelcfg/x/DebugPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/debug/panelcfg/x/DebugPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export type UpdateConfig = { render: boolean, diff --git a/packages/grafana-schema/src/raw/composable/elasticsearch/dataquery/x/ElasticsearchDataQuery_types.gen.ts b/packages/grafana-schema/src/raw/composable/elasticsearch/dataquery/x/ElasticsearchDataQuery_types.gen.ts index 57fa63d3a07..7cac1eab340 100644 --- a/packages/grafana-schema/src/raw/composable/elasticsearch/dataquery/x/ElasticsearchDataQuery_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/elasticsearch/dataquery/x/ElasticsearchDataQuery_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export type BucketAggregation = (DateHistogram | Histogram | Terms | Filters | GeoHashGrid | Nested); diff --git a/packages/grafana-schema/src/raw/composable/gauge/panelcfg/x/GaugePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/gauge/panelcfg/x/GaugePanelCfg_types.gen.ts index 0678c2947de..abe40c63645 100644 --- a/packages/grafana-schema/src/raw/composable/gauge/panelcfg/x/GaugePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/gauge/panelcfg/x/GaugePanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options extends common.SingleStatBaseOptions { minVizHeight: number; diff --git a/packages/grafana-schema/src/raw/composable/geomap/panelcfg/x/GeomapPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/geomap/panelcfg/x/GeomapPanelCfg_types.gen.ts index 8258f6d0501..14a718cf6d9 100644 --- a/packages/grafana-schema/src/raw/composable/geomap/panelcfg/x/GeomapPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/geomap/panelcfg/x/GeomapPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options { basemap: ui.MapLayerOptions; diff --git a/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts index b440943dacd..b87a6c3897f 100644 --- a/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; /** * Controls the color mode of the heatmap diff --git a/packages/grafana-schema/src/raw/composable/histogram/panelcfg/x/HistogramPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/histogram/panelcfg/x/HistogramPanelCfg_types.gen.ts index 4871a0e625b..819bb518581 100644 --- a/packages/grafana-schema/src/raw/composable/histogram/panelcfg/x/HistogramPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/histogram/panelcfg/x/HistogramPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options extends common.OptionsWithLegend, common.OptionsWithTooltip { /** diff --git a/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts index a73bc96ce5d..d8b98890e5e 100644 --- a/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options { controlsStorageKey?: string; diff --git a/packages/grafana-schema/src/raw/composable/logsnew/panelcfg/x/LogsNewPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/logsnew/panelcfg/x/LogsNewPanelCfg_types.gen.ts index 06c1a2c7735..8ac80fa1784 100644 --- a/packages/grafana-schema/src/raw/composable/logsnew/panelcfg/x/LogsNewPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/logsnew/panelcfg/x/LogsNewPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options { dedupStrategy: common.LogsDedupStrategy; diff --git a/packages/grafana-schema/src/raw/composable/loki/dataquery/x/LokiDataQuery_types.gen.ts b/packages/grafana-schema/src/raw/composable/loki/dataquery/x/LokiDataQuery_types.gen.ts index cc56fb954fa..59e8f4a13f7 100644 --- a/packages/grafana-schema/src/raw/composable/loki/dataquery/x/LokiDataQuery_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/loki/dataquery/x/LokiDataQuery_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export enum QueryEditorMode { Builder = 'builder', diff --git a/packages/grafana-schema/src/raw/composable/news/panelcfg/x/NewsPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/news/panelcfg/x/NewsPanelCfg_types.gen.ts index cbd6c366085..c86b8b987f7 100644 --- a/packages/grafana-schema/src/raw/composable/news/panelcfg/x/NewsPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/news/panelcfg/x/NewsPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options { /** diff --git a/packages/grafana-schema/src/raw/composable/nodegraph/panelcfg/x/NodeGraphPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/nodegraph/panelcfg/x/NodeGraphPanelCfg_types.gen.ts index db41a34dc9e..10f74d337b8 100644 --- a/packages/grafana-schema/src/raw/composable/nodegraph/panelcfg/x/NodeGraphPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/nodegraph/panelcfg/x/NodeGraphPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface ArcOption { /** diff --git a/packages/grafana-schema/src/raw/composable/piechart/panelcfg/x/PieChartPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/piechart/panelcfg/x/PieChartPanelCfg_types.gen.ts index e9dbbd45d5c..5061e824e18 100644 --- a/packages/grafana-schema/src/raw/composable/piechart/panelcfg/x/PieChartPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/piechart/panelcfg/x/PieChartPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; /** * Select the pie chart display style. diff --git a/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts index cac66616404..3f290835b85 100644 --- a/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options extends common.SingleStatBaseOptions { colorMode: common.BigValueColorMode; diff --git a/packages/grafana-schema/src/raw/composable/statetimeline/panelcfg/x/StateTimelinePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/statetimeline/panelcfg/x/StateTimelinePanelCfg_types.gen.ts index f3e32f729ee..40af7a986a7 100644 --- a/packages/grafana-schema/src/raw/composable/statetimeline/panelcfg/x/StateTimelinePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/statetimeline/panelcfg/x/StateTimelinePanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options extends ui.OptionsWithLegend, ui.OptionsWithTooltip, ui.OptionsWithTimezones { /** diff --git a/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts index 554a2e83491..d0cc207400b 100644 --- a/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options extends ui.OptionsWithLegend, ui.OptionsWithTooltip, ui.OptionsWithTimezones { /** diff --git a/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts index fb3d375fd29..2529b11f635 100644 --- a/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options { /** diff --git a/packages/grafana-schema/src/raw/composable/text/panelcfg/x/TextPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/text/panelcfg/x/TextPanelCfg_types.gen.ts index cf1dc84ee31..b97546048d7 100644 --- a/packages/grafana-schema/src/raw/composable/text/panelcfg/x/TextPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/text/panelcfg/x/TextPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export enum TextMode { Code = 'code', diff --git a/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts index 7cb398ca8de..b34b0b7e301 100644 --- a/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options extends common.OptionsWithTimezones { legend: common.VizLegendOptions; diff --git a/packages/grafana-schema/src/raw/composable/trend/panelcfg/x/TrendPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/trend/panelcfg/x/TrendPanelCfg_types.gen.ts index a0cb5ed211c..9d3946018df 100644 --- a/packages/grafana-schema/src/raw/composable/trend/panelcfg/x/TrendPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/trend/panelcfg/x/TrendPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; /** * Identical to timeseries... except it does not have timezone settings diff --git a/packages/grafana-schema/src/raw/composable/xychart/panelcfg/x/XYChartPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/xychart/panelcfg/x/XYChartPanelCfg_types.gen.ts index 9e86a2ac7de..35b7fdfd4e5 100644 --- a/packages/grafana-schema/src/raw/composable/xychart/panelcfg/x/XYChartPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/xychart/panelcfg/x/XYChartPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export enum PointShape { Circle = 'circle', diff --git a/packages/grafana-sql/package.json b/packages/grafana-sql/package.json index 7276014bd4b..8043f11ba0a 100644 --- a/packages/grafana-sql/package.json +++ b/packages/grafana-sql/package.json @@ -3,7 +3,7 @@ "license": "AGPL-3.0-only", "private": true, "name": "@grafana/sql", - "version": "12.0.0-pre", + "version": "12.1.0-pre", "repository": { "type": "git", "url": "http://github.com/grafana/grafana.git", @@ -15,11 +15,11 @@ }, "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.0.0-pre", - "@grafana/e2e-selectors": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", + "@grafana/e2e-selectors": "12.1.0-pre", "@grafana/plugin-ui": "0.10.5", - "@grafana/runtime": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/runtime": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "@react-awesome-query-builder/ui": "6.6.14", "immutable": "5.0.3", "lodash": "4.17.21", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 09a16356aef..3b82cdeb4a7 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/ui", - "version": "12.0.0-pre", + "version": "12.1.0-pre", "description": "Grafana Components Library", "keywords": [ "grafana", @@ -66,10 +66,10 @@ "@emotion/react": "11.14.0", "@emotion/serialize": "1.3.3", "@floating-ui/react": "0.27.7", - "@grafana/data": "12.0.0-pre", - "@grafana/e2e-selectors": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", + "@grafana/e2e-selectors": "12.1.0-pre", "@grafana/faro-web-sdk": "^1.13.2", - "@grafana/schema": "12.0.0-pre", + "@grafana/schema": "12.1.0-pre", "@hello-pangea/dnd": "17.0.0", "@leeoniya/ufuzzy": "1.0.18", "@monaco-editor/react": "4.6.0", diff --git a/public/app/plugins/datasource/azuremonitor/package.json b/public/app/plugins/datasource/azuremonitor/package.json index 69bcf471dc2..1519f574356 100644 --- a/public/app/plugins/datasource/azuremonitor/package.json +++ b/public/app/plugins/datasource/azuremonitor/package.json @@ -2,14 +2,14 @@ "name": "@grafana-plugins/grafana-azure-monitor-datasource", "description": "Grafana data source for Azure Monitor", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", "@grafana/plugin-ui": "0.10.5", - "@grafana/runtime": "12.0.0-pre", - "@grafana/schema": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/runtime": "12.1.0-pre", + "@grafana/schema": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "@kusto/monaco-kusto": "^10.0.0", "fast-deep-equal": "^3.1.3", "i18next": "^24.0.0", @@ -25,8 +25,8 @@ "tslib": "2.8.1" }, "devDependencies": { - "@grafana/e2e-selectors": "12.0.0-pre", - "@grafana/plugin-configs": "12.0.0-pre", + "@grafana/e2e-selectors": "12.1.0-pre", + "@grafana/plugin-configs": "12.1.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", "@testing-library/react": "16.2.0", diff --git a/public/app/plugins/datasource/cloud-monitoring/package.json b/public/app/plugins/datasource/cloud-monitoring/package.json index 1011936d833..8f0c6d7b214 100644 --- a/public/app/plugins/datasource/cloud-monitoring/package.json +++ b/public/app/plugins/datasource/cloud-monitoring/package.json @@ -2,15 +2,15 @@ "name": "@grafana-plugins/stackdriver", "description": "Grafana data source for Google Cloud Monitoring", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", "@grafana/google-sdk": "0.1.2", "@grafana/plugin-ui": "0.10.5", - "@grafana/runtime": "12.0.0-pre", - "@grafana/schema": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/runtime": "12.1.0-pre", + "@grafana/schema": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "debounce-promise": "3.1.2", "fast-deep-equal": "^3.1.3", "i18next": "^24.0.0", @@ -26,8 +26,8 @@ "tslib": "2.8.1" }, "devDependencies": { - "@grafana/e2e-selectors": "12.0.0-pre", - "@grafana/plugin-configs": "12.0.0-pre", + "@grafana/e2e-selectors": "12.1.0-pre", + "@grafana/plugin-configs": "12.1.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", "@testing-library/react": "16.2.0", diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json index c3d6868f5a9..b2772fbc7d1 100644 --- a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json +++ b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json @@ -2,22 +2,22 @@ "name": "@grafana-plugins/grafana-postgresql-datasource", "description": "PostgreSQL data source plugin", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", "@grafana/plugin-ui": "0.10.5", - "@grafana/runtime": "12.0.0-pre", - "@grafana/sql": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/runtime": "12.1.0-pre", + "@grafana/sql": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "lodash": "4.17.21", "react": "18.3.1", "rxjs": "7.8.1", "tslib": "2.8.1" }, "devDependencies": { - "@grafana/e2e-selectors": "12.0.0-pre", - "@grafana/plugin-configs": "12.0.0-pre", + "@grafana/e2e-selectors": "12.1.0-pre", + "@grafana/plugin-configs": "12.1.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/react": "16.2.0", "@testing-library/user-event": "14.6.1", diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json index 2a54f816399..dc3ac814652 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json @@ -2,13 +2,13 @@ "name": "@grafana-plugins/grafana-pyroscope-datasource", "description": "Continuous profiling for analysis of CPU and memory usage, down to the line number and throughout time. Saving infrastructure cost, improving performance, and increasing reliability.", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.0.0-pre", - "@grafana/runtime": "12.0.0-pre", - "@grafana/schema": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", + "@grafana/runtime": "12.1.0-pre", + "@grafana/schema": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "fast-deep-equal": "^3.1.3", "lodash": "4.17.21", "monaco-editor": "0.34.1", @@ -20,7 +20,7 @@ "tslib": "2.8.1" }, "devDependencies": { - "@grafana/plugin-configs": "12.0.0-pre", + "@grafana/plugin-configs": "12.1.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", "@testing-library/react": "16.2.0", diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/package.json b/public/app/plugins/datasource/grafana-testdata-datasource/package.json index c6e7abc0000..9a7bd250e1f 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/package.json +++ b/public/app/plugins/datasource/grafana-testdata-datasource/package.json @@ -2,13 +2,13 @@ "name": "@grafana-plugins/grafana-testdata-datasource", "description": "Generates test data in different forms", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.0.0-pre", - "@grafana/runtime": "12.0.0-pre", - "@grafana/schema": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", + "@grafana/runtime": "12.1.0-pre", + "@grafana/schema": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "d3-random": "^3.0.1", "lodash": "4.17.21", "micro-memoize": "^4.1.2", @@ -21,8 +21,8 @@ "uuid": "11.0.5" }, "devDependencies": { - "@grafana/e2e-selectors": "12.0.0-pre", - "@grafana/plugin-configs": "12.0.0-pre", + "@grafana/e2e-selectors": "12.1.0-pre", + "@grafana/plugin-configs": "12.1.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/react": "16.2.0", "@testing-library/user-event": "14.6.1", diff --git a/public/app/plugins/datasource/jaeger/package.json b/public/app/plugins/datasource/jaeger/package.json index d57ff4e08dc..264c033bd83 100644 --- a/public/app/plugins/datasource/jaeger/package.json +++ b/public/app/plugins/datasource/jaeger/package.json @@ -2,7 +2,7 @@ "name": "@grafana-plugins/jaeger", "description": "Jaeger plugin for Grafana", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "dependencies": { "@emotion/css": "11.13.5", "@grafana/data": "workspace:*", diff --git a/public/app/plugins/datasource/mssql/package.json b/public/app/plugins/datasource/mssql/package.json index 223c7845a45..2b975ac9ad9 100644 --- a/public/app/plugins/datasource/mssql/package.json +++ b/public/app/plugins/datasource/mssql/package.json @@ -2,22 +2,22 @@ "name": "@grafana-plugins/mssql", "description": "MSSQL data source plugin", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", "@grafana/plugin-ui": "0.10.5", - "@grafana/runtime": "12.0.0-pre", - "@grafana/sql": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/runtime": "12.1.0-pre", + "@grafana/sql": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "lodash": "4.17.21", "react": "18.3.1", "rxjs": "7.8.1", "tslib": "2.8.1" }, "devDependencies": { - "@grafana/e2e-selectors": "12.0.0-pre", - "@grafana/plugin-configs": "12.0.0-pre", + "@grafana/e2e-selectors": "12.1.0-pre", + "@grafana/plugin-configs": "12.1.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/react": "16.2.0", "@testing-library/user-event": "14.6.1", diff --git a/public/app/plugins/datasource/mysql/package.json b/public/app/plugins/datasource/mysql/package.json index 3e5ef53a73c..bd4ef505390 100644 --- a/public/app/plugins/datasource/mysql/package.json +++ b/public/app/plugins/datasource/mysql/package.json @@ -2,22 +2,22 @@ "name": "@grafana-plugins/mysql", "description": "MySQL data source plugin", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", "@grafana/plugin-ui": "0.10.5", - "@grafana/runtime": "12.0.0-pre", - "@grafana/sql": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/runtime": "12.1.0-pre", + "@grafana/sql": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "lodash": "4.17.21", "react": "18.3.1", "rxjs": "7.8.1", "tslib": "2.8.1" }, "devDependencies": { - "@grafana/e2e-selectors": "12.0.0-pre", - "@grafana/plugin-configs": "12.0.0-pre", + "@grafana/e2e-selectors": "12.1.0-pre", + "@grafana/plugin-configs": "12.1.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/react": "16.2.0", "@testing-library/user-event": "14.6.1", diff --git a/public/app/plugins/datasource/parca/package.json b/public/app/plugins/datasource/parca/package.json index f2c1816206d..cfe3ab5c6e7 100644 --- a/public/app/plugins/datasource/parca/package.json +++ b/public/app/plugins/datasource/parca/package.json @@ -2,13 +2,13 @@ "name": "@grafana-plugins/parca", "description": "Continuous profiling for analysis of CPU and memory usage, down to the line number and throughout time. Saving infrastructure cost, improving performance, and increasing reliability.", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.0.0-pre", - "@grafana/runtime": "12.0.0-pre", - "@grafana/schema": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", + "@grafana/runtime": "12.1.0-pre", + "@grafana/schema": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "lodash": "4.17.21", "monaco-editor": "0.34.1", "react": "18.3.1", @@ -18,7 +18,7 @@ "tslib": "2.8.1" }, "devDependencies": { - "@grafana/plugin-configs": "12.0.0-pre", + "@grafana/plugin-configs": "12.1.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/react": "16.2.0", "@testing-library/user-event": "14.6.1", diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json index ca4093dd1e7..df81acbef1f 100644 --- a/public/app/plugins/datasource/tempo/package.json +++ b/public/app/plugins/datasource/tempo/package.json @@ -2,7 +2,7 @@ "name": "@grafana-plugins/tempo", "description": "Grafana plugin for the Tempo data source.", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "dependencies": { "@emotion/css": "11.13.5", "@grafana/data": "workspace:*", @@ -39,7 +39,7 @@ "uuid": "11.0.5" }, "devDependencies": { - "@grafana/plugin-configs": "12.0.0-pre", + "@grafana/plugin-configs": "12.1.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", "@testing-library/react": "16.2.0", diff --git a/public/app/plugins/datasource/zipkin/package.json b/public/app/plugins/datasource/zipkin/package.json index 1e68d6d44e9..fa81f8a1d3f 100644 --- a/public/app/plugins/datasource/zipkin/package.json +++ b/public/app/plugins/datasource/zipkin/package.json @@ -2,7 +2,7 @@ "name": "@grafana-plugins/zipkin", "description": "Zipkin plugin for Grafana", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "dependencies": { "@emotion/css": "11.13.5", "@grafana/data": "workspace:*", diff --git a/yarn.lock b/yarn.lock index 74f9140805c..e674f1d8620 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2578,13 +2578,13 @@ __metadata: resolution: "@grafana-plugins/grafana-azure-monitor-datasource@workspace:public/app/plugins/datasource/azuremonitor" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.0.0-pre" - "@grafana/e2e-selectors": "npm:12.0.0-pre" - "@grafana/plugin-configs": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/e2e-selectors": "npm:12.1.0-pre" + "@grafana/plugin-configs": "npm:12.1.0-pre" "@grafana/plugin-ui": "npm:0.10.5" - "@grafana/runtime": "npm:12.0.0-pre" - "@grafana/schema": "npm:12.0.0-pre" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/runtime": "npm:12.1.0-pre" + "@grafana/schema": "npm:12.1.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@kusto/monaco-kusto": "npm:^10.0.0" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:6.6.3" @@ -2622,13 +2622,13 @@ __metadata: resolution: "@grafana-plugins/grafana-postgresql-datasource@workspace:public/app/plugins/datasource/grafana-postgresql-datasource" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.0.0-pre" - "@grafana/e2e-selectors": "npm:12.0.0-pre" - "@grafana/plugin-configs": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/e2e-selectors": "npm:12.1.0-pre" + "@grafana/plugin-configs": "npm:12.1.0-pre" "@grafana/plugin-ui": "npm:0.10.5" - "@grafana/runtime": "npm:12.0.0-pre" - "@grafana/sql": "npm:12.0.0-pre" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/runtime": "npm:12.1.0-pre" + "@grafana/sql": "npm:12.1.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@testing-library/dom": "npm:10.4.0" "@testing-library/react": "npm:16.2.0" "@testing-library/user-event": "npm:14.6.1" @@ -2653,11 +2653,11 @@ __metadata: resolution: "@grafana-plugins/grafana-pyroscope-datasource@workspace:public/app/plugins/datasource/grafana-pyroscope-datasource" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.0.0-pre" - "@grafana/plugin-configs": "npm:12.0.0-pre" - "@grafana/runtime": "npm:12.0.0-pre" - "@grafana/schema": "npm:12.0.0-pre" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/plugin-configs": "npm:12.1.0-pre" + "@grafana/runtime": "npm:12.1.0-pre" + "@grafana/schema": "npm:12.1.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:6.6.3" "@testing-library/react": "npm:16.2.0" @@ -2693,12 +2693,12 @@ __metadata: resolution: "@grafana-plugins/grafana-testdata-datasource@workspace:public/app/plugins/datasource/grafana-testdata-datasource" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.0.0-pre" - "@grafana/e2e-selectors": "npm:12.0.0-pre" - "@grafana/plugin-configs": "npm:12.0.0-pre" - "@grafana/runtime": "npm:12.0.0-pre" - "@grafana/schema": "npm:12.0.0-pre" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/e2e-selectors": "npm:12.1.0-pre" + "@grafana/plugin-configs": "npm:12.1.0-pre" + "@grafana/runtime": "npm:12.1.0-pre" + "@grafana/schema": "npm:12.1.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@testing-library/dom": "npm:10.4.0" "@testing-library/react": "npm:16.2.0" "@testing-library/user-event": "npm:14.6.1" @@ -2774,13 +2774,13 @@ __metadata: resolution: "@grafana-plugins/mssql@workspace:public/app/plugins/datasource/mssql" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.0.0-pre" - "@grafana/e2e-selectors": "npm:12.0.0-pre" - "@grafana/plugin-configs": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/e2e-selectors": "npm:12.1.0-pre" + "@grafana/plugin-configs": "npm:12.1.0-pre" "@grafana/plugin-ui": "npm:0.10.5" - "@grafana/runtime": "npm:12.0.0-pre" - "@grafana/sql": "npm:12.0.0-pre" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/runtime": "npm:12.1.0-pre" + "@grafana/sql": "npm:12.1.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@testing-library/dom": "npm:10.4.0" "@testing-library/react": "npm:16.2.0" "@testing-library/user-event": "npm:14.6.1" @@ -2805,13 +2805,13 @@ __metadata: resolution: "@grafana-plugins/mysql@workspace:public/app/plugins/datasource/mysql" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.0.0-pre" - "@grafana/e2e-selectors": "npm:12.0.0-pre" - "@grafana/plugin-configs": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/e2e-selectors": "npm:12.1.0-pre" + "@grafana/plugin-configs": "npm:12.1.0-pre" "@grafana/plugin-ui": "npm:0.10.5" - "@grafana/runtime": "npm:12.0.0-pre" - "@grafana/sql": "npm:12.0.0-pre" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/runtime": "npm:12.1.0-pre" + "@grafana/sql": "npm:12.1.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@testing-library/dom": "npm:10.4.0" "@testing-library/react": "npm:16.2.0" "@testing-library/user-event": "npm:14.6.1" @@ -2836,11 +2836,11 @@ __metadata: resolution: "@grafana-plugins/parca@workspace:public/app/plugins/datasource/parca" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.0.0-pre" - "@grafana/plugin-configs": "npm:12.0.0-pre" - "@grafana/runtime": "npm:12.0.0-pre" - "@grafana/schema": "npm:12.0.0-pre" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/plugin-configs": "npm:12.1.0-pre" + "@grafana/runtime": "npm:12.1.0-pre" + "@grafana/schema": "npm:12.1.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@testing-library/dom": "npm:10.4.0" "@testing-library/react": "npm:16.2.0" "@testing-library/user-event": "npm:14.6.1" @@ -2868,14 +2868,14 @@ __metadata: resolution: "@grafana-plugins/stackdriver@workspace:public/app/plugins/datasource/cloud-monitoring" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.0.0-pre" - "@grafana/e2e-selectors": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/e2e-selectors": "npm:12.1.0-pre" "@grafana/google-sdk": "npm:0.1.2" - "@grafana/plugin-configs": "npm:12.0.0-pre" + "@grafana/plugin-configs": "npm:12.1.0-pre" "@grafana/plugin-ui": "npm:0.10.5" - "@grafana/runtime": "npm:12.0.0-pre" - "@grafana/schema": "npm:12.0.0-pre" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/runtime": "npm:12.1.0-pre" + "@grafana/schema": "npm:12.1.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:6.6.3" "@testing-library/react": "npm:16.2.0" @@ -2920,7 +2920,7 @@ __metadata: "@grafana/lezer-traceql": "npm:0.0.21" "@grafana/monaco-logql": "npm:^0.0.8" "@grafana/o11y-ds-frontend": "workspace:*" - "@grafana/plugin-configs": "npm:12.0.0-pre" + "@grafana/plugin-configs": "npm:12.1.0-pre" "@grafana/plugin-ui": "npm:0.10.5" "@grafana/runtime": "workspace:*" "@grafana/schema": "workspace:*" @@ -3056,12 +3056,12 @@ __metadata: languageName: node linkType: hard -"@grafana/data@npm:12.0.0-pre, @grafana/data@workspace:*, @grafana/data@workspace:packages/grafana-data": +"@grafana/data@npm:12.1.0-pre, @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": "npm:7.0.1" - "@grafana/schema": "npm:12.0.0-pre" + "@grafana/schema": "npm:12.1.0-pre" "@grafana/tsconfig": "npm:^2.0.0" "@rollup/plugin-node-resolve": "npm:16.0.0" "@types/d3-interpolate": "npm:^3.0.0" @@ -3109,7 +3109,7 @@ __metadata: languageName: unknown linkType: soft -"@grafana/e2e-selectors@npm:12.0.0-pre, @grafana/e2e-selectors@workspace:*, @grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors": +"@grafana/e2e-selectors@npm:12.1.0-pre, @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: @@ -3211,9 +3211,9 @@ __metadata: "@babel/preset-env": "npm:7.26.9" "@babel/preset-react": "npm:7.26.3" "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" "@grafana/tsconfig": "npm:^2.0.0" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@leeoniya/ufuzzy": "npm:1.0.18" "@rollup/plugin-node-resolve": "npm:16.0.0" "@testing-library/dom": "npm:10.4.0" @@ -3320,13 +3320,13 @@ __metadata: resolution: "@grafana/o11y-ds-frontend@workspace:packages/grafana-o11y-ds-frontend" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.0.0-pre" - "@grafana/e2e-selectors": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/e2e-selectors": "npm:12.1.0-pre" "@grafana/plugin-ui": "npm:0.10.5" - "@grafana/runtime": "npm:12.0.0-pre" - "@grafana/schema": "npm:12.0.0-pre" + "@grafana/runtime": "npm:12.1.0-pre" + "@grafana/schema": "npm:12.1.0-pre" "@grafana/tsconfig": "npm:^2.0.0" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:^6.1.2" "@testing-library/react": "npm:16.2.0" @@ -3350,7 +3350,7 @@ __metadata: languageName: unknown linkType: soft -"@grafana/plugin-configs@npm:12.0.0-pre, @grafana/plugin-configs@workspace:*, @grafana/plugin-configs@workspace:packages/grafana-plugin-configs": +"@grafana/plugin-configs@npm:12.1.0-pre, @grafana/plugin-configs@workspace:*, @grafana/plugin-configs@workspace:packages/grafana-plugin-configs": version: 0.0.0-use.local resolution: "@grafana/plugin-configs@workspace:packages/grafana-plugin-configs" dependencies: @@ -3450,13 +3450,13 @@ __metadata: dependencies: "@emotion/css": "npm:11.13.5" "@floating-ui/react": "npm:0.27.7" - "@grafana/data": "npm:12.0.0-pre" - "@grafana/e2e-selectors": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/e2e-selectors": "npm:12.1.0-pre" "@grafana/plugin-ui": "npm:0.10.5" - "@grafana/runtime": "npm:12.0.0-pre" - "@grafana/schema": "npm:12.0.0-pre" + "@grafana/runtime": "npm:12.1.0-pre" + "@grafana/schema": "npm:12.1.0-pre" "@grafana/tsconfig": "npm:^2.0.0" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@hello-pangea/dnd": "npm:17.0.0" "@leeoniya/ufuzzy": "npm:1.0.18" "@lezer/common": "npm:1.2.3" @@ -3512,16 +3512,16 @@ __metadata: languageName: unknown linkType: soft -"@grafana/runtime@npm:12.0.0-pre, @grafana/runtime@workspace:*, @grafana/runtime@workspace:packages/grafana-runtime": +"@grafana/runtime@npm:12.1.0-pre, @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": "npm:12.0.0-pre" - "@grafana/e2e-selectors": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/e2e-selectors": "npm:12.1.0-pre" "@grafana/faro-web-sdk": "npm:^1.13.2" - "@grafana/schema": "npm:12.0.0-pre" + "@grafana/schema": "npm:12.1.0-pre" "@grafana/tsconfig": "npm:^2.0.0" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@rollup/plugin-node-resolve": "npm:16.0.0" "@rollup/plugin-terser": "npm:0.4.4" "@testing-library/dom": "npm:10.4.0" @@ -3599,7 +3599,7 @@ __metadata: languageName: node linkType: hard -"@grafana/schema@npm:12.0.0-pre, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema": +"@grafana/schema@npm:12.1.0-pre, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema": version: 0.0.0-use.local resolution: "@grafana/schema@workspace:packages/grafana-schema" dependencies: @@ -3617,17 +3617,17 @@ __metadata: languageName: unknown linkType: soft -"@grafana/sql@npm:12.0.0-pre, @grafana/sql@workspace:*, @grafana/sql@workspace:packages/grafana-sql": +"@grafana/sql@npm:12.1.0-pre, @grafana/sql@workspace:*, @grafana/sql@workspace:packages/grafana-sql": version: 0.0.0-use.local resolution: "@grafana/sql@workspace:packages/grafana-sql" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.0.0-pre" - "@grafana/e2e-selectors": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/e2e-selectors": "npm:12.1.0-pre" "@grafana/plugin-ui": "npm:0.10.5" - "@grafana/runtime": "npm:12.0.0-pre" + "@grafana/runtime": "npm:12.1.0-pre" "@grafana/tsconfig": "npm:^2.0.0" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@react-awesome-query-builder/ui": "npm:6.6.14" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:^6.1.2" @@ -3668,7 +3668,7 @@ __metadata: languageName: node linkType: hard -"@grafana/ui@npm:12.0.0-pre, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui": +"@grafana/ui@npm:12.1.0-pre, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui": version: 0.0.0-use.local resolution: "@grafana/ui@workspace:packages/grafana-ui" dependencies: @@ -3678,10 +3678,10 @@ __metadata: "@emotion/serialize": "npm:1.3.3" "@faker-js/faker": "npm:^9.0.0" "@floating-ui/react": "npm:0.27.7" - "@grafana/data": "npm:12.0.0-pre" - "@grafana/e2e-selectors": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/e2e-selectors": "npm:12.1.0-pre" "@grafana/faro-web-sdk": "npm:^1.13.2" - "@grafana/schema": "npm:12.0.0-pre" + "@grafana/schema": "npm:12.1.0-pre" "@grafana/tsconfig": "npm:^2.0.0" "@hello-pangea/dnd": "npm:17.0.0" "@leeoniya/ufuzzy": "npm:1.0.18" From 674fdd1d323ef041dbed93b44ae58afaaf1a8b64 Mon Sep 17 00:00:00 2001 From: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com> Date: Thu, 24 Apr 2025 16:49:59 +0200 Subject: [PATCH 099/146] Alerting: Add alertingBulkActionsInUI feature toggle (#104470) Add alertingBulkActionsInUI feature toggle --- .../src/types/featureToggles.gen.ts | 5 +++++ pkg/services/featuremgmt/registry.go | 10 ++++++++++ pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 ++++ pkg/services/featuremgmt/toggles_gen.json | 19 +++++++++++++++++++ 5 files changed, 39 insertions(+) diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 2f86e020d67..0a2bf78e6c9 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -1027,4 +1027,9 @@ export interface FeatureToggles { * @default false */ alertRuleUseFiredAtForStartsAt?: boolean; + /** + * Enables the alerting bulk actions in the UI + * @default true + */ + alertingBulkActionsInUI?: boolean; } diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 087b7327b81..bceb66b4f24 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1768,6 +1768,16 @@ var ( Owner: grafanaAlertingSquad, Expression: "false", }, + { + Name: "alertingBulkActionsInUI", + Description: "Enables the alerting bulk actions in the UI", + FrontendOnly: true, + Stage: FeatureStageGeneralAvailability, + Owner: grafanaAlertingSquad, + HideFromAdminPage: true, + HideFromDocs: true, + Expression: "true", // enabled by default + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index fe9151fc18c..a1b2babf74c 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -231,3 +231,4 @@ metricsFromProfiles,experimental,@grafana/observability-traces-and-profiling,fal pluginsAutoUpdate,experimental,@grafana/plugins-platform-backend,false,false,false alertingListViewV2PreviewToggle,privatePreview,@grafana/alerting-squad,false,false,true alertRuleUseFiredAtForStartsAt,experimental,@grafana/alerting-squad,false,false,false +alertingBulkActionsInUI,GA,@grafana/alerting-squad,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 3d2d2f8cfcb..76018e01faf 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -934,4 +934,8 @@ const ( // FlagAlertRuleUseFiredAtForStartsAt // Use FiredAt for StartsAt when sending alerts to Alertmaanger FlagAlertRuleUseFiredAtForStartsAt = "alertRuleUseFiredAtForStartsAt" + + // FlagAlertingBulkActionsInUI + // Enables the alerting bulk actions in the UI + FlagAlertingBulkActionsInUI = "alertingBulkActionsInUI" ) diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 4a11a5109c7..60e2842e568 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -96,6 +96,25 @@ "codeowner": "@grafana/alerting-squad" } }, + { + "metadata": { + "name": "alertingBulkActionsInUI", + "resourceVersion": "1745504721038", + "creationTimestamp": "2025-04-24T14:01:56Z", + "annotations": { + "grafana.app/updatedTimestamp": "2025-04-24 14:25:21.03825 +0000 UTC" + } + }, + "spec": { + "description": "Enables the alerting bulk actions in the UI", + "stage": "GA", + "codeowner": "@grafana/alerting-squad", + "frontend": true, + "hideFromAdminPage": true, + "hideFromDocs": true, + "expression": "true" + } + }, { "metadata": { "name": "alertingCentralAlertHistory", From 109267ab03f16ebe9849625929ebfe13ecfd9db3 Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Thu, 24 Apr 2025 17:58:17 +0200 Subject: [PATCH 100/146] Alerting: Remove feature toggle for custom recovery threshold (#104455) --- .../feature-toggles/index.md | 1 - .../src/types/featureToggles.gen.ts | 5 - pkg/expr/nodes.go | 2 +- pkg/expr/reader.go | 2 +- pkg/expr/threshold.go | 5 +- pkg/expr/threshold_test.go | 5 +- pkg/services/featuremgmt/registry.go | 9 - pkg/services/featuremgmt/toggles_gen.csv | 1 - pkg/services/featuremgmt/toggles_gen.go | 4 - pkg/services/featuremgmt/toggles_gen.json | 516 ++++++++---------- pkg/services/ngalert/eval/eval_test.go | 2 +- pkg/tests/api/alerting/api_ruler_test.go | 2 +- .../expressions/components/Threshold.tsx | 5 +- 13 files changed, 247 insertions(+), 312 deletions(-) diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 3472114015f..f340ab99a01 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -46,7 +46,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `panelMonitoring` | Enables panel monitoring through logs and measurements | Yes | | `formatString` | Enable format string transformer | Yes | | `kubernetesClientDashboardsFolders` | Route the folder and dashboard service requests to k8s | Yes | -| `recoveryThreshold` | Enables feature recovery threshold (aka hysteresis) for threshold server-side expression | Yes | | `lokiStructuredMetadata` | Enables the loki data source to request structured metadata from the Loki server | Yes | | `addFieldFromCalculationStatFunctions` | Add cumulative and window functions to the add field from calculation transformation | Yes | | `annotationPermissionUpdate` | Change the way annotation permissions work by scoping them to folders and dashboards. | Yes | diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 0a2bf78e6c9..c86709ef862 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -354,11 +354,6 @@ export interface FeatureToggles { */ cloudWatchBatchQueries?: boolean; /** - * Enables feature recovery threshold (aka hysteresis) for threshold server-side expression - * @default true - */ - recoveryThreshold?: boolean; - /** * Enables the loki data source to request structured metadata from the Loki server * @default true */ diff --git a/pkg/expr/nodes.go b/pkg/expr/nodes.go index b39e17a1384..660a0a6b8fc 100644 --- a/pkg/expr/nodes.go +++ b/pkg/expr/nodes.go @@ -161,7 +161,7 @@ func buildCMDNode(rn *rawNode, toggles featuremgmt.FeatureToggles, sqlExpression case TypeClassicConditions: node.Command, err = classic.UnmarshalConditionsCmd(rn.Query, rn.RefID) case TypeThreshold: - node.Command, err = UnmarshalThresholdCommand(rn, toggles) + node.Command, err = UnmarshalThresholdCommand(rn) case TypeSQL: node.Command, err = UnmarshalSQLCommand(rn, sqlExpressionCellLimit) default: diff --git a/pkg/expr/reader.go b/pkg/expr/reader.go index 6d1349ae84c..c108b913ccd 100644 --- a/pkg/expr/reader.go +++ b/pkg/expr/reader.go @@ -158,7 +158,7 @@ func (h *ExpressionQueryReader) ReadQuery( eq.Command = threshold eq.Properties = q - if firstCondition.UnloadEvaluator != nil && h.features.IsEnabledGlobally(featuremgmt.FlagRecoveryThreshold) { + if firstCondition.UnloadEvaluator != nil { unloading, err := NewThresholdCommand(common.RefID, referenceVar, firstCondition.UnloadEvaluator.Type, firstCondition.UnloadEvaluator.Params) unloading.Invert = true if err != nil { diff --git a/pkg/expr/threshold.go b/pkg/expr/threshold.go index f9e9656e03e..b23e26f57dd 100644 --- a/pkg/expr/threshold.go +++ b/pkg/expr/threshold.go @@ -13,7 +13,6 @@ import ( "github.com/grafana/grafana/pkg/expr/mathexp" "github.com/grafana/grafana/pkg/expr/metrics" "github.com/grafana/grafana/pkg/infra/tracing" - "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/util" ) @@ -131,7 +130,7 @@ type ConditionEvalJSON struct { } // UnmarshalResampleCommand creates a ResampleCMD from Grafana's frontend query. -func UnmarshalThresholdCommand(rn *rawNode, features featuremgmt.FeatureToggles) (Command, error) { +func UnmarshalThresholdCommand(rn *rawNode) (Command, error) { cmdConfig := ThresholdCommandConfig{} if err := json.Unmarshal(rn.QueryRaw, &cmdConfig); err != nil { return nil, fmt.Errorf("failed to parse the threshold command: %w", err) @@ -151,7 +150,7 @@ func UnmarshalThresholdCommand(rn *rawNode, features featuremgmt.FeatureToggles) if err != nil { return nil, fmt.Errorf("invalid condition: %w", err) } - if firstCondition.UnloadEvaluator != nil && features.IsEnabledGlobally(featuremgmt.FlagRecoveryThreshold) { + if firstCondition.UnloadEvaluator != nil { unloading, err := NewThresholdCommand(rn.RefID, referenceVar, firstCondition.UnloadEvaluator.Type, firstCondition.UnloadEvaluator.Params) if err != nil { return nil, fmt.Errorf("invalid unloadCondition: %w", err) diff --git a/pkg/expr/threshold_test.go b/pkg/expr/threshold_test.go index 0bdc695e7ed..58f162c9ce0 100644 --- a/pkg/expr/threshold_test.go +++ b/pkg/expr/threshold_test.go @@ -15,7 +15,6 @@ import ( "github.com/grafana/grafana/pkg/expr/mathexp" "github.com/grafana/grafana/pkg/infra/tracing" - "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/util" ) @@ -277,7 +276,7 @@ func TestUnmarshalThresholdCommand(t *testing.T) { QueryRaw: []byte(tc.query), QueryType: "", DataSource: nil, - }, featuremgmt.WithFeatures(featuremgmt.FlagRecoveryThreshold)) + }) if tc.shouldError { require.Nil(t, cmd) @@ -466,7 +465,7 @@ func TestSetLoadedDimensionsToHysteresisCommand(t *testing.T) { cmd, err := UnmarshalThresholdCommand(&rawNode{ RefID: "B", QueryRaw: raw, - }, featuremgmt.WithFeatures(featuremgmt.FlagRecoveryThreshold)) + }) require.NoError(t, err) require.Equal(t, fingerprints, cmd.(*HysteresisCommand).LoadedDimensions) diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index bceb66b4f24..682068ca1dc 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -590,15 +590,6 @@ var ( Stage: FeatureStagePublicPreview, Owner: awsDatasourcesSquad, }, - { - Name: "recoveryThreshold", - Description: "Enables feature recovery threshold (aka hysteresis) for threshold server-side expression", - Stage: FeatureStageGeneralAvailability, - FrontendOnly: false, - Owner: grafanaAlertingSquad, - RequiresRestart: true, - Expression: "true", - }, { Name: "lokiStructuredMetadata", Description: "Enables the loki data source to request structured metadata from the Loki server", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index a1b2babf74c..e29e7610d6e 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -77,7 +77,6 @@ queryServiceRewrite,experimental,@grafana/grafana-datasources-core-services,fals queryServiceFromUI,experimental,@grafana/grafana-datasources-core-services,false,false,true queryServiceFromExplore,experimental,@grafana/grafana-datasources-core-services,false,false,true cloudWatchBatchQueries,preview,@grafana/aws-datasources,false,false,false -recoveryThreshold,GA,@grafana/alerting-squad,false,true,false lokiStructuredMetadata,GA,@grafana/observability-logs,false,false,false cachingOptimizeSerializationMemoryUsage,experimental,@grafana/grafana-operator-experience-squad,false,false,false prometheusCodeModeMetricNamesSearch,experimental,@grafana/oss-big-tent,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 76018e01faf..c86dea8143f 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -319,10 +319,6 @@ const ( // Runs CloudWatch metrics queries as separate batches FlagCloudWatchBatchQueries = "cloudWatchBatchQueries" - // FlagRecoveryThreshold - // Enables feature recovery threshold (aka hysteresis) for threshold server-side expression - FlagRecoveryThreshold = "recoveryThreshold" - // FlagLokiStructuredMetadata // Enables the loki data source to request structured metadata from the Loki server FlagLokiStructuredMetadata = "lokiStructuredMetadata" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 60e2842e568..c20b13fad3e 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -6,7 +6,7 @@ { "metadata": { "name": "ABTestFeatureToggleA", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-01-13T21:13:13Z" }, "spec": { @@ -20,7 +20,7 @@ { "metadata": { "name": "ABTestFeatureToggleB", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-01-13T21:13:13Z" }, "spec": { @@ -34,7 +34,7 @@ { "metadata": { "name": "addFieldFromCalculationStatFunctions", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-03T14:39:58Z" }, "spec": { @@ -48,7 +48,7 @@ { "metadata": { "name": "aiGeneratedDashboardChanges", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-03-05T12:01:31Z" }, "spec": { @@ -61,7 +61,7 @@ { "metadata": { "name": "alertRuleRestore", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-05T14:15:26Z" }, "spec": { @@ -74,8 +74,8 @@ { "metadata": { "name": "alertRuleUseFiredAtForStartsAt", - "resourceVersion": "1745339544057", - "creationTimestamp": "2025-04-22T16:32:24Z" + "resourceVersion": "1745491786560", + "creationTimestamp": "2025-04-24T10:49:46Z" }, "spec": { "description": "Use FiredAt for StartsAt when sending alerts to Alertmaanger", @@ -87,7 +87,7 @@ { "metadata": { "name": "alertingBacktesting", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-12-14T14:44:14Z" }, "spec": { @@ -118,7 +118,7 @@ { "metadata": { "name": "alertingCentralAlertHistory", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-05-29T15:01:38Z" }, "spec": { @@ -131,7 +131,7 @@ { "metadata": { "name": "alertingDisableSendAlertsExternal", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-05-23T12:29:19Z" }, "spec": { @@ -145,7 +145,7 @@ { "metadata": { "name": "alertingFilterV2", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-09-11T11:29:26Z" }, "spec": { @@ -158,7 +158,7 @@ { "metadata": { "name": "alertingInsights", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-09-14T12:58:04Z" }, "spec": { @@ -173,7 +173,7 @@ { "metadata": { "name": "alertingJiraIntegration", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-02-14T12:22:04Z" }, "spec": { @@ -187,7 +187,7 @@ { "metadata": { "name": "alertingListViewV2", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-05-24T14:40:49Z" }, "spec": { @@ -200,8 +200,8 @@ { "metadata": { "name": "alertingListViewV2PreviewToggle", - "resourceVersion": "1745339544057", - "creationTimestamp": "2025-04-22T16:32:24Z" + "resourceVersion": "1745491786560", + "creationTimestamp": "2025-04-24T10:49:46Z" }, "spec": { "description": "Enables the alerting list view v2 preview toggle", @@ -213,7 +213,7 @@ { "metadata": { "name": "alertingMigrationUI", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-14T16:40:05Z" }, "spec": { @@ -228,7 +228,7 @@ { "metadata": { "name": "alertingNotificationsStepMode", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-22T11:07:45Z" }, "spec": { @@ -242,7 +242,7 @@ { "metadata": { "name": "alertingPrometheusRulesPrimary", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-09-27T12:27:16Z" }, "spec": { @@ -255,7 +255,7 @@ { "metadata": { "name": "alertingQueryAndExpressionsStepMode", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-09-26T06:33:14Z" }, "spec": { @@ -269,7 +269,7 @@ { "metadata": { "name": "alertingQueryOptimization", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-01-10T20:52:58Z" }, "spec": { @@ -282,7 +282,7 @@ { "metadata": { "name": "alertingRulePermanentlyDelete", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-03T11:18:25Z" }, "spec": { @@ -298,7 +298,7 @@ { "metadata": { "name": "alertingRuleRecoverDeleted", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-27T14:39:26Z" }, "spec": { @@ -314,7 +314,7 @@ { "metadata": { "name": "alertingRuleVersionHistoryRestore", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-02-17T12:25:32Z" }, "spec": { @@ -330,7 +330,7 @@ { "metadata": { "name": "alertingSaveStateCompressed", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-01-27T17:47:33Z" }, "spec": { @@ -343,7 +343,7 @@ { "metadata": { "name": "alertingSaveStatePeriodic", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-01-23T16:03:30Z" }, "spec": { @@ -355,7 +355,7 @@ { "metadata": { "name": "alertingSimplifiedRouting", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-10T13:14:39Z" }, "spec": { @@ -368,7 +368,7 @@ { "metadata": { "name": "alertingUIOptimizeReducer", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-18T10:59:00Z" }, "spec": { @@ -382,7 +382,7 @@ { "metadata": { "name": "alertmanagerRemoteOnly", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-10-30T16:27:08Z" }, "spec": { @@ -394,7 +394,7 @@ { "metadata": { "name": "alertmanagerRemotePrimary", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-10-30T16:27:08Z" }, "spec": { @@ -406,7 +406,7 @@ { "metadata": { "name": "alertmanagerRemoteSecondary", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-10-30T16:27:08Z" }, "spec": { @@ -418,7 +418,7 @@ { "metadata": { "name": "angularDeprecationUI", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-08-29T14:05:47Z" }, "spec": { @@ -432,7 +432,7 @@ { "metadata": { "name": "annotationPermissionUpdate", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-10-31T13:30:13Z" }, "spec": { @@ -445,7 +445,7 @@ { "metadata": { "name": "appPlatformGrpcClientAuth", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-10-14T10:47:18Z" }, "spec": { @@ -459,7 +459,7 @@ { "metadata": { "name": "assetSriChecks", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-04T10:56:35Z" }, "spec": { @@ -472,7 +472,7 @@ { "metadata": { "name": "authZGRPCServer", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-06-13T09:41:35Z" }, "spec": { @@ -486,7 +486,7 @@ { "metadata": { "name": "awsAsyncQueryCaching", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-07-21T15:34:07Z" }, "spec": { @@ -499,7 +499,7 @@ { "metadata": { "name": "awsDatasourcesTempCredentials", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-07-06T15:06:11Z" }, "spec": { @@ -511,7 +511,7 @@ { "metadata": { "name": "azureMonitorDisableLogLimit", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-10-24T13:32:09Z" }, "spec": { @@ -524,7 +524,7 @@ { "metadata": { "name": "azureMonitorEnableUserAuth", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-27T14:01:54Z" }, "spec": { @@ -537,7 +537,7 @@ { "metadata": { "name": "azureMonitorLogsBuilderEditor", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-02T14:15:25Z" }, "spec": { @@ -550,7 +550,7 @@ { "metadata": { "name": "azureMonitorPrometheusExemplars", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-06-06T16:53:17Z" }, "spec": { @@ -563,7 +563,7 @@ { "metadata": { "name": "cachingOptimizeSerializationMemoryUsage", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-10-12T16:56:49Z" }, "spec": { @@ -575,7 +575,7 @@ { "metadata": { "name": "canvasPanelNesting", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-05-31T19:03:34Z" }, "spec": { @@ -589,7 +589,7 @@ { "metadata": { "name": "canvasPanelPanZoom", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-01-02T19:52:21Z" }, "spec": { @@ -602,7 +602,7 @@ { "metadata": { "name": "cloudRBACRoles", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-01-10T13:19:01Z" }, "spec": { @@ -618,7 +618,7 @@ { "metadata": { "name": "cloudWatchBatchQueries", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-10-20T19:09:41Z" }, "spec": { @@ -630,7 +630,7 @@ { "metadata": { "name": "cloudWatchCrossAccountQuerying", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-11-28T11:39:12Z" }, "spec": { @@ -644,7 +644,7 @@ { "metadata": { "name": "cloudWatchNewLabelParsing", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-04-05T15:57:56Z" }, "spec": { @@ -657,7 +657,7 @@ { "metadata": { "name": "cloudWatchRoundUpEndTime", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-06-27T15:10:28Z" }, "spec": { @@ -670,7 +670,7 @@ { "metadata": { "name": "configurableSchedulerTick", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-07-26T16:44:12Z" }, "spec": { @@ -684,7 +684,7 @@ { "metadata": { "name": "correlations", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-09-16T13:14:27Z" }, "spec": { @@ -698,7 +698,7 @@ { "metadata": { "name": "crashDetection", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-12T15:07:27Z" }, "spec": { @@ -711,7 +711,7 @@ { "metadata": { "name": "dashboardDisableSchemaValidationV1", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-11T16:52:46Z" }, "spec": { @@ -723,7 +723,7 @@ { "metadata": { "name": "dashboardDisableSchemaValidationV2", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-11T16:52:46Z" }, "spec": { @@ -735,7 +735,7 @@ { "metadata": { "name": "dashboardNewLayouts", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-10-23T08:55:45Z" }, "spec": { @@ -748,7 +748,7 @@ { "metadata": { "name": "dashboardScene", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-13T08:51:21Z" }, "spec": { @@ -762,7 +762,7 @@ { "metadata": { "name": "dashboardSceneForViewers", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-02T19:02:25Z" }, "spec": { @@ -776,7 +776,7 @@ { "metadata": { "name": "dashboardSceneSolo", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-02-11T08:08:47Z" }, "spec": { @@ -790,7 +790,7 @@ { "metadata": { "name": "dashboardSchemaValidationLogging", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-11T16:52:46Z" }, "spec": { @@ -802,7 +802,7 @@ { "metadata": { "name": "dashgpt", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-08-30T20:22:05Z" }, "spec": { @@ -816,7 +816,7 @@ { "metadata": { "name": "dataplaneAggregator", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-08-09T08:41:07Z" }, "spec": { @@ -829,7 +829,7 @@ { "metadata": { "name": "dataplaneFrontendFallback", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-04-07T21:13:19Z" }, "spec": { @@ -844,7 +844,7 @@ { "metadata": { "name": "datasourceAPIServers", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-09-19T08:28:27Z" }, "spec": { @@ -857,7 +857,7 @@ { "metadata": { "name": "datasourceConnectionsTab", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-01-21T17:39:48Z" }, "spec": { @@ -870,7 +870,7 @@ { "metadata": { "name": "datasourceQueryTypes", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-05-23T16:46:28Z" }, "spec": { @@ -883,7 +883,7 @@ { "metadata": { "name": "disableClassicHTTPHistogram", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-06-18T19:37:44Z" }, "spec": { @@ -897,7 +897,7 @@ { "metadata": { "name": "disableEnvelopeEncryption", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-05-24T08:34:47Z" }, "spec": { @@ -911,7 +911,7 @@ { "metadata": { "name": "disableNumericMetricsSortingInExpressions", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-04-16T14:52:47Z" }, "spec": { @@ -924,7 +924,7 @@ { "metadata": { "name": "disableSSEDataplane", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-04-12T16:24:34Z" }, "spec": { @@ -936,7 +936,7 @@ { "metadata": { "name": "editPanelCSVDragAndDrop", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-01-24T09:43:44Z" }, "spec": { @@ -949,7 +949,7 @@ { "metadata": { "name": "elasticsearchCrossClusterSearch", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-12-12T22:20:04Z" }, "spec": { @@ -961,7 +961,7 @@ { "metadata": { "name": "elasticsearchImprovedParsing", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-01-15T17:05:54Z" }, "spec": { @@ -973,7 +973,7 @@ { "metadata": { "name": "enableDatagridEditing", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-04-24T14:46:31Z" }, "spec": { @@ -986,7 +986,7 @@ { "metadata": { "name": "enableExtensionsAdminPage", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-05T15:55:10Z" }, "spec": { @@ -999,7 +999,7 @@ { "metadata": { "name": "enableNativeHTTPHistogram", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-10-03T18:23:55Z" }, "spec": { @@ -1013,7 +1013,7 @@ { "metadata": { "name": "enableSCIM", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-07T14:38:46Z" }, "spec": { @@ -1025,7 +1025,7 @@ { "metadata": { "name": "enableScopesInMetricsExplore", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-06T13:11:33Z" }, "spec": { @@ -1039,7 +1039,7 @@ { "metadata": { "name": "exploreLogsAggregatedMetrics", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-08-29T13:55:59Z" }, "spec": { @@ -1052,7 +1052,7 @@ { "metadata": { "name": "exploreLogsLimitedTimeRange", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-08-29T13:55:59Z" }, "spec": { @@ -1065,7 +1065,7 @@ { "metadata": { "name": "exploreLogsShardSplitting", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-08-29T13:55:59Z" }, "spec": { @@ -1078,7 +1078,7 @@ { "metadata": { "name": "exploreMetricsRelatedLogs", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-05T16:28:43Z" }, "spec": { @@ -1091,7 +1091,7 @@ { "metadata": { "name": "expressionParser", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-02-17T00:59:11Z" }, "spec": { @@ -1104,7 +1104,7 @@ { "metadata": { "name": "extensionSidebar", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-03T10:16:35Z" }, "spec": { @@ -1117,7 +1117,7 @@ { "metadata": { "name": "externalCorePlugins", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-09-22T08:50:13Z" }, "spec": { @@ -1130,7 +1130,7 @@ { "metadata": { "name": "externalServiceAccounts", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-09-28T07:26:37Z" }, "spec": { @@ -1143,7 +1143,7 @@ { "metadata": { "name": "extraThemes", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-05-10T13:37:04Z" }, "spec": { @@ -1156,7 +1156,7 @@ { "metadata": { "name": "extractFieldsNameDeduplication", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-02T15:47:42Z" }, "spec": { @@ -1169,7 +1169,7 @@ { "metadata": { "name": "failWrongDSUID", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-06-20T10:56:39Z" }, "spec": { @@ -1182,7 +1182,7 @@ { "metadata": { "name": "faroDatasourceSelector", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-05-05T00:35:10Z" }, "spec": { @@ -1195,7 +1195,7 @@ { "metadata": { "name": "featureHighlights", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-02-03T11:53:23Z" }, "spec": { @@ -1209,7 +1209,7 @@ { "metadata": { "name": "featureToggleAdminPage", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-07-18T20:43:32Z" }, "spec": { @@ -1223,7 +1223,7 @@ { "metadata": { "name": "feedbackButton", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-12-02T17:08:15Z" }, "spec": { @@ -1236,7 +1236,7 @@ { "metadata": { "name": "fetchRulesUsingPost", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-01-29T12:17:44Z" }, "spec": { @@ -1250,7 +1250,7 @@ { "metadata": { "name": "formatString", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-10-13T18:17:12Z" }, "spec": { @@ -1264,7 +1264,7 @@ { "metadata": { "name": "grafanaAPIServerEnsureKubectlAccess", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-12-06T20:21:21Z" }, "spec": { @@ -1278,7 +1278,7 @@ { "metadata": { "name": "grafanaAPIServerWithExperimentalAPIs", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-10-06T18:55:22Z" }, "spec": { @@ -1292,7 +1292,7 @@ { "metadata": { "name": "grafanaAdvisor", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-01-20T10:08:00Z" }, "spec": { @@ -1304,7 +1304,7 @@ { "metadata": { "name": "grafanaManagedRecordingRules", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-04-22T17:53:16Z" }, "spec": { @@ -1318,7 +1318,7 @@ { "metadata": { "name": "grafanaManagedRecordingRulesDatasources", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-07T13:30:40Z" }, "spec": { @@ -1332,7 +1332,7 @@ { "metadata": { "name": "grafanaconThemes", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-02-06T11:08:04Z" }, "spec": { @@ -1348,7 +1348,7 @@ { "metadata": { "name": "groupAttributeSync", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-09-09T15:29:43Z" }, "spec": { @@ -1361,7 +1361,7 @@ { "metadata": { "name": "groupByVariable", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-02-14T17:18:04Z" }, "spec": { @@ -1375,7 +1375,7 @@ { "metadata": { "name": "groupToNestedTableTransformation", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-02-07T14:28:26Z" }, "spec": { @@ -1389,7 +1389,7 @@ { "metadata": { "name": "grpcServer", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-09-26T20:25:34Z" }, "spec": { @@ -1402,7 +1402,7 @@ { "metadata": { "name": "homeSetupGuide", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-09-25T17:20:04Z" }, "spec": { @@ -1415,7 +1415,7 @@ { "metadata": { "name": "improvedExternalSessionHandling", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-09-17T10:54:39Z" }, "spec": { @@ -1428,7 +1428,7 @@ { "metadata": { "name": "improvedExternalSessionHandlingSAML", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-01-09T17:02:49Z" }, "spec": { @@ -1441,7 +1441,7 @@ { "metadata": { "name": "individualCookiePreferences", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-02-21T10:19:07Z" }, "spec": { @@ -1453,7 +1453,7 @@ { "metadata": { "name": "infinityRunQueriesInParallel", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-14T12:54:04Z" }, "spec": { @@ -1465,7 +1465,7 @@ { "metadata": { "name": "influxdbBackendMigration", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-02-09T18:26:16Z", "deletionTimestamp": "2023-01-17T14:11:26Z" }, @@ -1480,7 +1480,7 @@ { "metadata": { "name": "influxdbRunQueriesInParallel", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-02-01T10:58:24Z" }, "spec": { @@ -1492,7 +1492,7 @@ { "metadata": { "name": "influxqlStreamingParser", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-29T17:29:35Z" }, "spec": { @@ -1504,7 +1504,7 @@ { "metadata": { "name": "investigationsBackend", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-12-18T08:31:03Z" }, "spec": { @@ -1517,7 +1517,7 @@ { "metadata": { "name": "inviteUserExperimental", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-07T19:09:59Z" }, "spec": { @@ -1532,7 +1532,7 @@ { "metadata": { "name": "jaegerBackendMigration", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-15T14:40:20Z" }, "spec": { @@ -1544,7 +1544,7 @@ { "metadata": { "name": "jitterAlertRulesWithinGroups", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-01-18T18:48:11Z" }, "spec": { @@ -1558,7 +1558,7 @@ { "metadata": { "name": "k8SFolderCounts", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-12-27T17:10:44Z" }, "spec": { @@ -1571,7 +1571,7 @@ { "metadata": { "name": "k8SFolderMove", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-12-27T17:10:44Z" }, "spec": { @@ -1584,7 +1584,7 @@ { "metadata": { "name": "kubernetesAggregator", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-02-12T20:59:35Z" }, "spec": { @@ -1597,7 +1597,7 @@ { "metadata": { "name": "kubernetesClientDashboardsFolders", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-02-18T23:11:26Z" }, "spec": { @@ -1610,7 +1610,7 @@ { "metadata": { "name": "kubernetesDashboards", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-06-05T14:34:23Z" }, "spec": { @@ -1623,7 +1623,7 @@ { "metadata": { "name": "kubernetesFeatureToggles", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-01-18T05:32:44Z" }, "spec": { @@ -1637,7 +1637,7 @@ { "metadata": { "name": "kubernetesSnapshots", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-12-05T22:31:49Z" }, "spec": { @@ -1650,7 +1650,7 @@ { "metadata": { "name": "libraryPanelRBAC", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-10-11T23:30:50Z" }, "spec": { @@ -1663,7 +1663,7 @@ { "metadata": { "name": "localeFormatPreference", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-31T13:59:07Z" }, "spec": { @@ -1675,7 +1675,7 @@ { "metadata": { "name": "localizationForPlugins", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-31T04:38:38Z" }, "spec": { @@ -1687,7 +1687,7 @@ { "metadata": { "name": "logQLScope", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-11T11:53:24Z" }, "spec": { @@ -1702,7 +1702,7 @@ { "metadata": { "name": "logRequestsInstrumentedAsUnknown", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-06-10T08:56:55Z" }, "spec": { @@ -1714,7 +1714,7 @@ { "metadata": { "name": "logRowsPopoverMenu", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-16T09:48:10Z" }, "spec": { @@ -1728,7 +1728,7 @@ { "metadata": { "name": "logsContextDatasourceUi", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-01-27T14:12:01Z" }, "spec": { @@ -1743,7 +1743,7 @@ { "metadata": { "name": "logsExploreTableDefaultVisualization", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-05-02T15:28:15Z" }, "spec": { @@ -1756,7 +1756,7 @@ { "metadata": { "name": "logsExploreTableVisualisation", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-07-12T13:52:42Z" }, "spec": { @@ -1770,7 +1770,7 @@ { "metadata": { "name": "logsInfiniteScrolling", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-09T10:54:03Z" }, "spec": { @@ -1784,7 +1784,7 @@ { "metadata": { "name": "logsPanelControls", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-07T14:38:55Z" }, "spec": { @@ -1798,7 +1798,7 @@ { "metadata": { "name": "lokiExperimentalStreaming", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-06-19T10:03:51Z" }, "spec": { @@ -1810,7 +1810,7 @@ { "metadata": { "name": "lokiLabelNamesQueryApi", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-12-13T14:31:41Z" }, "spec": { @@ -1823,7 +1823,7 @@ { "metadata": { "name": "lokiLogsDataplane", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-07-13T07:58:00Z" }, "spec": { @@ -1835,7 +1835,7 @@ { "metadata": { "name": "lokiPredefinedOperations", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-06-02T10:52:36Z" }, "spec": { @@ -1848,7 +1848,7 @@ { "metadata": { "name": "lokiQueryHints", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-12-18T20:43:16Z" }, "spec": { @@ -1862,7 +1862,7 @@ { "metadata": { "name": "lokiQuerySplitting", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-02-09T17:27:02Z" }, "spec": { @@ -1877,7 +1877,7 @@ { "metadata": { "name": "lokiQuerySplittingConfig", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-03-20T15:51:36Z" }, "spec": { @@ -1890,7 +1890,7 @@ { "metadata": { "name": "lokiRunQueriesInParallel", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-09-19T09:34:01Z" }, "spec": { @@ -1902,7 +1902,7 @@ { "metadata": { "name": "lokiSendDashboardPanelNames", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-08-22T19:30:43Z" }, "spec": { @@ -1914,7 +1914,7 @@ { "metadata": { "name": "lokiShardSplitting", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-10-23T11:21:03Z" }, "spec": { @@ -1927,7 +1927,7 @@ { "metadata": { "name": "lokiStructuredMetadata", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-16T16:06:14Z" }, "spec": { @@ -1940,7 +1940,7 @@ { "metadata": { "name": "managedDualWriter", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-02-19T14:50:39Z" }, "spec": { @@ -1954,7 +1954,7 @@ { "metadata": { "name": "metricsFromProfiles", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-09T10:55:28Z" }, "spec": { @@ -1967,7 +1967,7 @@ { "metadata": { "name": "mlExpressions", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-07-13T17:37:50Z" }, "spec": { @@ -1979,7 +1979,7 @@ { "metadata": { "name": "multiTenantTempCredentials", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-02T20:25:50Z" }, "spec": { @@ -1992,7 +1992,7 @@ { "metadata": { "name": "mysqlAnsiQuotes", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-10-12T11:43:35Z" }, "spec": { @@ -2004,7 +2004,7 @@ { "metadata": { "name": "nestedFolders", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-10-26T14:15:14Z" }, "spec": { @@ -2017,7 +2017,7 @@ { "metadata": { "name": "newDashboardSharingComponent", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-05-03T15:02:18Z" }, "spec": { @@ -2031,7 +2031,7 @@ { "metadata": { "name": "newDashboardWithFiltersAndGroupBy", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-04-04T11:25:21Z" }, "spec": { @@ -2045,7 +2045,7 @@ { "metadata": { "name": "newFiltersUI", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-08-30T12:48:13Z" }, "spec": { @@ -2058,7 +2058,7 @@ { "metadata": { "name": "newFolderPicker", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-01-15T11:43:19Z" }, "spec": { @@ -2071,7 +2071,7 @@ { "metadata": { "name": "newLogsPanel", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-02-04T17:40:17Z" }, "spec": { @@ -2084,7 +2084,7 @@ { "metadata": { "name": "newPDFRendering", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-02-08T12:09:34Z" }, "spec": { @@ -2097,7 +2097,7 @@ { "metadata": { "name": "newShareReportDrawer", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-02-17T19:05:46Z" }, "spec": { @@ -2111,7 +2111,7 @@ { "metadata": { "name": "oauthRequireSubClaim", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-03-25T13:22:24Z" }, "spec": { @@ -2125,7 +2125,7 @@ { "metadata": { "name": "onPremToCloudMigrations", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-01-22T16:09:08Z" }, "spec": { @@ -2138,7 +2138,7 @@ { "metadata": { "name": "panelFilterVariable", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-03T12:15:54Z" }, "spec": { @@ -2152,7 +2152,7 @@ { "metadata": { "name": "panelMonitoring", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-10-09T05:19:08Z" }, "spec": { @@ -2166,7 +2166,7 @@ { "metadata": { "name": "panelTitleSearch", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-02-15T18:26:03Z" }, "spec": { @@ -2179,7 +2179,7 @@ { "metadata": { "name": "passwordlessMagicLinkAuthentication", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-14T13:50:55Z" }, "spec": { @@ -2193,7 +2193,7 @@ { "metadata": { "name": "pdfTables", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-06T13:39:22Z" }, "spec": { @@ -2205,7 +2205,7 @@ { "metadata": { "name": "permissionsFilterRemoveSubquery", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-08-02T07:39:25Z" }, "spec": { @@ -2217,7 +2217,7 @@ { "metadata": { "name": "pinNavItems", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-06-10T11:40:03Z" }, "spec": { @@ -2230,7 +2230,7 @@ { "metadata": { "name": "playlistsReconciler", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-12-20T03:09:31Z" }, "spec": { @@ -2243,7 +2243,7 @@ { "metadata": { "name": "pluginProxyPreserveTrailingSlash", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-06-05T11:36:14Z" }, "spec": { @@ -2256,8 +2256,8 @@ { "metadata": { "name": "pluginsAutoUpdate", - "resourceVersion": "1745339544057", - "creationTimestamp": "2025-04-22T16:32:24Z" + "resourceVersion": "1745491786560", + "creationTimestamp": "2025-04-24T10:49:46Z" }, "spec": { "description": "Enables auto-updating of users installed plugins", @@ -2268,7 +2268,7 @@ { "metadata": { "name": "pluginsCDNSyncLoader", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-02-07T10:07:08Z" }, "spec": { @@ -2280,7 +2280,7 @@ { "metadata": { "name": "pluginsDetailsRightPanel", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-08-13T09:55:30Z" }, "spec": { @@ -2294,7 +2294,7 @@ { "metadata": { "name": "pluginsFrontendSandbox", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-06-05T08:51:36Z" }, "spec": { @@ -2306,7 +2306,7 @@ { "metadata": { "name": "pluginsSkipHostEnvVars", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-15T17:09:14Z" }, "spec": { @@ -2318,7 +2318,7 @@ { "metadata": { "name": "pluginsSriChecks", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-10-04T12:55:09Z" }, "spec": { @@ -2331,7 +2331,7 @@ { "metadata": { "name": "preinstallAutoUpdate", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-07T12:14:25Z" }, "spec": { @@ -2344,7 +2344,7 @@ { "metadata": { "name": "preserveDashboardStateWhenNavigating", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-05-27T12:28:06Z" }, "spec": { @@ -2358,7 +2358,7 @@ { "metadata": { "name": "promQLScope", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-01-29T20:22:17Z" }, "spec": { @@ -2373,7 +2373,7 @@ { "metadata": { "name": "prometheusAzureOverrideAudience", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-05-30T15:43:32Z", "deletionTimestamp": "2023-07-16T21:30:14Z" }, @@ -2387,7 +2387,7 @@ { "metadata": { "name": "prometheusCodeModeMetricNamesSearch", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-04-04T20:38:23Z" }, "spec": { @@ -2400,7 +2400,7 @@ { "metadata": { "name": "prometheusSpecialCharsInLabelValues", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-12-18T21:31:08Z" }, "spec": { @@ -2410,24 +2410,10 @@ "frontend": true } }, - { - "metadata": { - "name": "prometheusUsesCombobox", - "resourceVersion": "1743693517832", - "creationTimestamp": "2024-10-23T11:18:33Z", - "deletionTimestamp": "2025-04-11T19:25:28Z" - }, - "spec": { - "description": "Use new **Combobox** component for Prometheus query editor", - "stage": "GA", - "codeowner": "@grafana/oss-big-tent", - "expression": "true" - } - }, { "metadata": { "name": "provisioning", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-22T09:03:50Z" }, "spec": { @@ -2440,7 +2426,7 @@ { "metadata": { "name": "publicDashboardsEmailSharing", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-01-03T19:45:15Z" }, "spec": { @@ -2454,7 +2440,7 @@ { "metadata": { "name": "publicDashboardsScene", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-03-22T14:48:21Z" }, "spec": { @@ -2468,7 +2454,7 @@ { "metadata": { "name": "queryLibrary", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-10-07T18:31:45Z", "deletionTimestamp": "2023-03-20T16:00:14Z" }, @@ -2481,11 +2467,8 @@ { "metadata": { "name": "queryService", - "resourceVersion": "1745480536808", - "creationTimestamp": "2024-04-19T09:26:21Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-04-24 07:42:16.80869 +0000 UTC" - } + "resourceVersion": "1745491786560", + "creationTimestamp": "2024-04-19T09:26:21Z" }, "spec": { "description": "Register /apis/query.grafana.app/ -- will eventually replace /api/ds/query", @@ -2497,7 +2480,7 @@ { "metadata": { "name": "queryServiceFromExplore", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-02T10:00:33Z" }, "spec": { @@ -2510,11 +2493,8 @@ { "metadata": { "name": "queryServiceFromUI", - "resourceVersion": "1745480536808", - "creationTimestamp": "2024-04-19T09:26:21Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-04-24 07:42:16.80869 +0000 UTC" - } + "resourceVersion": "1745491786560", + "creationTimestamp": "2024-04-19T09:26:21Z" }, "spec": { "description": "Routes requests to the new query service", @@ -2526,11 +2506,8 @@ { "metadata": { "name": "queryServiceRewrite", - "resourceVersion": "1745480536808", - "creationTimestamp": "2024-04-19T09:26:21Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-04-24 07:42:16.80869 +0000 UTC" - } + "resourceVersion": "1745491786560", + "creationTimestamp": "2024-04-19T09:26:21Z" }, "spec": { "description": "Rewrite requests targeting /ds/query to the query service", @@ -2542,7 +2519,7 @@ { "metadata": { "name": "recordedQueriesMulti", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-06-14T12:34:22Z" }, "spec": { @@ -2552,24 +2529,10 @@ "expression": "true" } }, - { - "metadata": { - "name": "recoveryThreshold", - "resourceVersion": "1745339544057", - "creationTimestamp": "2023-10-10T14:51:50Z" - }, - "spec": { - "description": "Enables feature recovery threshold (aka hysteresis) for threshold server-side expression", - "stage": "GA", - "codeowner": "@grafana/alerting-squad", - "requiresRestart": true, - "expression": "true" - } - }, { "metadata": { "name": "refactorVariablesTimeRange", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-06-06T13:12:09Z" }, "spec": { @@ -2582,7 +2545,7 @@ { "metadata": { "name": "regressionTransformation", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-24T14:49:16Z" }, "spec": { @@ -2595,7 +2558,7 @@ { "metadata": { "name": "reloadDashboardsOnParamsChange", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-10-25T12:56:54Z" }, "spec": { @@ -2609,7 +2572,7 @@ { "metadata": { "name": "renderAuthJWT", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-04-03T16:53:38Z" }, "spec": { @@ -2622,7 +2585,7 @@ { "metadata": { "name": "rendererDisableAppPluginsPreload", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-02-24T14:43:06Z" }, "spec": { @@ -2637,7 +2600,7 @@ { "metadata": { "name": "reportingRetries", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-08-31T07:47:47Z" }, "spec": { @@ -2650,7 +2613,7 @@ { "metadata": { "name": "reportingUseRawTimeRange", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-14T20:08:03Z" }, "spec": { @@ -2663,7 +2626,7 @@ { "metadata": { "name": "rolePickerDrawer", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-09-26T12:51:38Z" }, "spec": { @@ -2675,7 +2638,7 @@ { "metadata": { "name": "scopeApi", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-27T07:58:25Z" }, "spec": { @@ -2689,7 +2652,7 @@ { "metadata": { "name": "scopeFilters", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-03-05T15:41:19Z" }, "spec": { @@ -2703,7 +2666,7 @@ { "metadata": { "name": "scopeSearchAllLevels", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-14T07:42:16Z" }, "spec": { @@ -2717,7 +2680,7 @@ { "metadata": { "name": "secretsManagementAppPlatform", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-19T09:25:14Z" }, "spec": { @@ -2729,7 +2692,7 @@ { "metadata": { "name": "showDashboardValidationWarnings", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-10-14T13:51:05Z" }, "spec": { @@ -2741,7 +2704,7 @@ { "metadata": { "name": "sqlDatasourceDatabaseSelection", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-06-06T16:28:52Z" }, "spec": { @@ -2755,7 +2718,7 @@ { "metadata": { "name": "sqlExpressions", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-02-27T21:16:00Z" }, "spec": { @@ -2767,7 +2730,7 @@ { "metadata": { "name": "sseGroupByDatasource", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-09-07T20:02:07Z" }, "spec": { @@ -2779,7 +2742,7 @@ { "metadata": { "name": "ssoSettingsApi", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-08T09:50:01Z" }, "spec": { @@ -2793,7 +2756,7 @@ { "metadata": { "name": "ssoSettingsLDAP", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-06-18T11:31:27Z" }, "spec": { @@ -2807,7 +2770,7 @@ { "metadata": { "name": "ssoSettingsSAML", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-03-14T11:04:45Z" }, "spec": { @@ -2821,7 +2784,7 @@ { "metadata": { "name": "storage", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-03-17T17:19:23Z" }, "spec": { @@ -2833,7 +2796,7 @@ { "metadata": { "name": "tableNextGen", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-26T03:57:57Z" }, "spec": { @@ -2845,7 +2808,7 @@ { "metadata": { "name": "tableSharedCrosshair", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-12-13T09:33:14Z" }, "spec": { @@ -2858,7 +2821,7 @@ { "metadata": { "name": "teamHttpHeadersMimir", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-01-13T10:42:47Z" }, "spec": { @@ -2871,7 +2834,7 @@ { "metadata": { "name": "templateVariablesUsesCombobox", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-01-31T09:53:13Z" }, "spec": { @@ -2884,7 +2847,7 @@ { "metadata": { "name": "timeRangeProvider", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-10-22T10:52:33Z" }, "spec": { @@ -2896,7 +2859,7 @@ { "metadata": { "name": "tlsMemcached", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-05-09T19:12:08Z" }, "spec": { @@ -2909,7 +2872,7 @@ { "metadata": { "name": "transformationsRedesign", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-07-12T16:35:49Z" }, "spec": { @@ -2924,7 +2887,7 @@ { "metadata": { "name": "unifiedHistory", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-12-13T10:41:18Z" }, "spec": { @@ -2937,7 +2900,7 @@ { "metadata": { "name": "unifiedNavbars", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-09T12:51:22Z" }, "spec": { @@ -2951,7 +2914,7 @@ { "metadata": { "name": "unifiedRequestLog", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-03-31T13:38:09Z" }, "spec": { @@ -2965,7 +2928,7 @@ { "metadata": { "name": "unifiedStorageBigObjectsSupport", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-10-17T10:18:29Z" }, "spec": { @@ -2977,7 +2940,7 @@ { "metadata": { "name": "unifiedStorageGrpcConnectionPool", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-21T13:24:54Z" }, "spec": { @@ -2991,11 +2954,8 @@ { "metadata": { "name": "unifiedStorageHistoryPruner", - "resourceVersion": "1745478115689", - "creationTimestamp": "2025-03-17T10:36:38Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-04-24 07:01:55.689179 +0000 UTC" - } + "resourceVersion": "1745491786560", + "creationTimestamp": "2025-03-17T10:36:38Z" }, "spec": { "description": "Enables the unified storage history pruner", @@ -3009,7 +2969,7 @@ { "metadata": { "name": "unifiedStorageSearch", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-09-30T19:46:14Z" }, "spec": { @@ -3023,7 +2983,7 @@ { "metadata": { "name": "unifiedStorageSearchPermissionFiltering", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-01-22T11:38:37Z" }, "spec": { @@ -3038,7 +2998,7 @@ { "metadata": { "name": "unifiedStorageSearchSprinkles", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-12-18T17:00:54Z" }, "spec": { @@ -3052,7 +3012,7 @@ { "metadata": { "name": "unifiedStorageSearchUI", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-12-19T18:21:48Z" }, "spec": { @@ -3066,7 +3026,7 @@ { "metadata": { "name": "useScopesNavigationEndpoint", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-31T15:20:00Z" }, "spec": { @@ -3081,7 +3041,7 @@ { "metadata": { "name": "useSessionStorageForRedirection", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-09-23T09:31:23Z" }, "spec": { @@ -3094,7 +3054,7 @@ { "metadata": { "name": "wargamesTesting", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-09-13T18:32:01Z" }, "spec": { @@ -3106,7 +3066,7 @@ { "metadata": { "name": "xrayApplicationSignals", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-01T14:42:02Z" }, "spec": { @@ -3121,7 +3081,7 @@ { "metadata": { "name": "zanzana", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-06-19T13:59:47Z" }, "spec": { diff --git a/pkg/services/ngalert/eval/eval_test.go b/pkg/services/ngalert/eval/eval_test.go index 80da967dbb8..9f82f958b03 100644 --- a/pkg/services/ngalert/eval/eval_test.go +++ b/pkg/services/ngalert/eval/eval_test.go @@ -710,7 +710,7 @@ func TestCreate_HysteresisCommand(t *testing.T) { cache: cacheService, pluginsStore: store, }) - evaluator := NewEvaluatorFactory(setting.UnifiedAlertingSettings{}, cacheService, expr.ProvideService(&setting.Cfg{ExpressionsEnabled: true}, nil, nil, featuremgmt.WithFeatures(featuremgmt.FlagRecoveryThreshold), nil, tracing.InitializeTracerForTest())) + evaluator := NewEvaluatorFactory(setting.UnifiedAlertingSettings{}, cacheService, expr.ProvideService(&setting.Cfg{ExpressionsEnabled: true}, nil, nil, featuremgmt.WithFeatures(), nil, tracing.InitializeTracerForTest())) evalCtx := NewContextWithPreviousResults(context.Background(), u, testCase.reader) eval, err := evaluator.Create(evalCtx, condition) diff --git a/pkg/tests/api/alerting/api_ruler_test.go b/pkg/tests/api/alerting/api_ruler_test.go index 4dec75a1021..c7e47d14677 100644 --- a/pkg/tests/api/alerting/api_ruler_test.go +++ b/pkg/tests/api/alerting/api_ruler_test.go @@ -4297,7 +4297,7 @@ func TestIntegrationHysteresisRule(t *testing.T) { DisableAnonymous: true, AppModeProduction: true, NGAlertSchedulerBaseInterval: 1 * time.Second, - EnableFeatureToggles: []string{featuremgmt.FlagConfigurableSchedulerTick, featuremgmt.FlagRecoveryThreshold}, + EnableFeatureToggles: []string{featuremgmt.FlagConfigurableSchedulerTick}, }) grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p) diff --git a/public/app/features/expressions/components/Threshold.tsx b/public/app/features/expressions/components/Threshold.tsx index 8f4782241dc..e3fa883d918 100644 --- a/public/app/features/expressions/components/Threshold.tsx +++ b/public/app/features/expressions/components/Threshold.tsx @@ -6,7 +6,6 @@ import { FormEvent, useEffect, useReducer } from 'react'; import { GrafanaTheme2, SelectableValue } from '@grafana/data'; import { InlineField, InlineFieldRow, InlineSwitch, Input, Select, Stack, useStyles2 } from '@grafana/ui'; -import { config } from 'app/core/config'; import { t } from 'app/core/internationalization'; import { EvalFunction } from 'app/features/alerting/state/alertDef'; @@ -86,8 +85,6 @@ export const Threshold = ({ labelWidth, onChange, refIds, query, onError, useHys conditionInState.evaluator.type === EvalFunction.IsOutsideRangeIncluded || conditionInState.evaluator.type === EvalFunction.IsWithinRangeIncluded; - const hysteresisEnabled = Boolean(config.featureToggles?.recoveryThreshold) && useHysteresis; - const id = uniqueId('threshold-'); return ( @@ -125,7 +122,7 @@ export const Threshold = ({ labelWidth, onChange, refIds, query, onError, useHys /> )} - {hysteresisEnabled && } + {useHysteresis && } ); interface HysteresisSectionProps { From 040a82c81580b774ee42fe67b0c1cadd34acce33 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Thu, 24 Apr 2025 18:33:09 +0200 Subject: [PATCH 101/146] Alerting: Add an internal label to rules converted from Prometheus (#104475) --- pkg/services/ngalert/models/alert_rule.go | 4 ++++ pkg/services/ngalert/prom/convert.go | 5 ++++- pkg/services/ngalert/prom/convert_test.go | 23 ++++++++++++++++------- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/pkg/services/ngalert/models/alert_rule.go b/pkg/services/ngalert/models/alert_rule.go index fb5ecad3970..3f95cfd8b62 100644 --- a/pkg/services/ngalert/models/alert_rule.go +++ b/pkg/services/ngalert/models/alert_rule.go @@ -153,6 +153,10 @@ const ( // MigratedMessageAnnotation is created during legacy migration to store the migrated alert message. MigratedMessageAnnotation = "message" + // ConvertedPrometheusRuleLabel is a label that indicates that the alert rule was converted from a Prometheus rule + // using the import APIs. + ConvertedPrometheusRuleLabel = "__converted_prometheus_rule__" + // AutogeneratedRouteLabel a label name used to distinguish alerts that are supposed to be handled by the autogenerated policy. Only expected value is `true`. AutogeneratedRouteLabel = "__grafana_autogenerated__" // AutogeneratedRouteReceiverNameLabel a label name that contains the name of the receiver that should be used to send notifications for the alert. diff --git a/pkg/services/ngalert/prom/convert.go b/pkg/services/ngalert/prom/convert.go index df0adedbff5..e4ab9ce0a60 100644 --- a/pkg/services/ngalert/prom/convert.go +++ b/pkg/services/ngalert/prom/convert.go @@ -231,10 +231,13 @@ func (p *Converter) convertRule(orgID int64, namespaceUID string, promGroup Prom title = rule.Alert } - labels := make(map[string]string, len(rule.Labels)+len(promGroup.Labels)) + labels := make(map[string]string, len(rule.Labels)+len(promGroup.Labels)+1) maps.Copy(labels, promGroup.Labels) maps.Copy(labels, rule.Labels) + // Add a special label to indicate that this rule was converted from a Prometheus rule. + labels[models.ConvertedPrometheusRuleLabel] = "true" + originalRuleDefinition, err := yaml.Marshal(rule) if err != nil { return models.AlertRule{}, fmt.Errorf("failed to marshal original rule definition: %w", err) diff --git a/pkg/services/ngalert/prom/convert_test.go b/pkg/services/ngalert/prom/convert_test.go index 9fed80776e7..1d4668d2b27 100644 --- a/pkg/services/ngalert/prom/convert_test.go +++ b/pkg/services/ngalert/prom/convert_test.go @@ -322,6 +322,7 @@ func TestPrometheusRulesToGrafana(t *testing.T) { expectedLabels := make(map[string]string, len(promRule.Labels)+len(tc.promGroup.Labels)) maps.Copy(expectedLabels, tc.promGroup.Labels) maps.Copy(expectedLabels, promRule.Labels) + expectedLabels = withInternalLabel(expectedLabels) uidData := fmt.Sprintf("%d|%s|%s|%d", tc.orgID, tc.namespace, tc.promGroup.Name, j) u := uuid.NewSHA1(uuid.NameSpaceOID, []byte(uidData)) @@ -550,11 +551,11 @@ func TestPrometheusRulesToGrafana_GroupLabels(t *testing.T) { // Check that the labels are merged and the rule label takes precedence require.Equal( t, - map[string]string{ + withInternalLabel(map[string]string{ "group_label": "group_value", "rule_label": "rule_value", "common_label": "rule_value", - }, + }), grafanaGroup.Rules[0].Labels, ) }) @@ -586,11 +587,11 @@ func TestPrometheusRulesToGrafana_GroupLabels(t *testing.T) { // Check that the labels are merged and the rule label takes precedence require.Equal( t, - map[string]string{ + withInternalLabel(map[string]string{ "group_label": "group_value", "rule_label": "rule_value", "common_label": "rule_value", - }, + }), grafanaGroup.Rules[0].Labels, ) }) @@ -614,8 +615,7 @@ func TestPrometheusRulesToGrafana_GroupLabels(t *testing.T) { grafanaGroup, err := converter.PrometheusRulesToGrafana(1, "namespace", promGroup) require.NoError(t, err) require.Len(t, grafanaGroup.Rules, 1) - - require.Equal(t, promGroup.Labels, grafanaGroup.Rules[0].Labels) + require.Equal(t, withInternalLabel(promGroup.Labels), grafanaGroup.Rules[0].Labels) }) t.Run("rule and group with nil labels", func(t *testing.T) { @@ -633,7 +633,7 @@ func TestPrometheusRulesToGrafana_GroupLabels(t *testing.T) { grafanaGroup, err := converter.PrometheusRulesToGrafana(1, "namespace", promGroup) require.NoError(t, err) require.Len(t, grafanaGroup.Rules, 1) - require.Empty(t, grafanaGroup.Rules[0].Labels) + require.Equal(t, withInternalLabel(map[string]string{}), grafanaGroup.Rules[0].Labels) }) } @@ -847,3 +847,12 @@ func TestQueryModelContainsRequiredParameters(t *testing.T) { require.True(t, isNumber, "maxDataPoints should be a number") } } + +func withInternalLabel(l map[string]string) map[string]string { + result := map[string]string{ + models.ConvertedPrometheusRuleLabel: "true", + } + maps.Copy(result, l) + + return result +} From 5a10e3b43e1936fa20552bb2772cac4ceb83ac6f Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Thu, 24 Apr 2025 17:36:12 +0100 Subject: [PATCH 102/146] Select: Fix minor layout shift when opening/closing the menu (#104476) fix width of magnifying glass causing layout shift --- packages/grafana-ui/src/components/Select/DropdownIndicator.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grafana-ui/src/components/Select/DropdownIndicator.tsx b/packages/grafana-ui/src/components/Select/DropdownIndicator.tsx index ed0f4213544..c95d2e32e2c 100644 --- a/packages/grafana-ui/src/components/Select/DropdownIndicator.tsx +++ b/packages/grafana-ui/src/components/Select/DropdownIndicator.tsx @@ -5,6 +5,6 @@ import { Icon } from '../Icon/Icon'; export function DropdownIndicator({ selectProps }: DropdownIndicatorProps) { const isOpen = selectProps.menuIsOpen; const icon = isOpen ? 'search' : 'angle-down'; - const size = isOpen ? 'sm' : 'md'; + const size = 'md'; return ; } From a6735721bf1ffddf8d40d56aea3133decc1dac88 Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Thu, 24 Apr 2025 14:01:00 -0500 Subject: [PATCH 103/146] XYChart: Coerce threshold steps to numbers (#104485) --- public/app/plugins/panel/xychart/scatter.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/app/plugins/panel/xychart/scatter.ts b/public/app/plugins/panel/xychart/scatter.ts index d7bb53a3838..e9f36d8ed73 100644 --- a/public/app/plugins/panel/xychart/scatter.ts +++ b/public/app/plugins/panel/xychart/scatter.ts @@ -641,7 +641,8 @@ function fieldValueColors(f: Field, theme: GrafanaTheme2): FieldColorValues { let lasti = steps.length - 1; for (let i = lasti; i > 0; i--) { - conds += `v >= ${steps[i].value} ? ${i} : `; + let rhs = Number(steps[i].value); + conds += `v >= ${rhs} ? ${i} : `; } conds += '0'; From 1f707d16ed5de802e91c8d0122dce681f68cf9eb Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Thu, 24 Apr 2025 15:15:17 -0400 Subject: [PATCH 104/146] Apply security patch 357-202503311017.patch (#104490) * Sanitize paths before evaluating access to route * use util.CleanRelativePath --------- Co-authored-by: Andres Martinez Gotor --- pkg/api/pluginproxy/ds_proxy.go | 10 +++++++++- pkg/api/pluginproxy/ds_proxy_test.go | 8 ++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/pkg/api/pluginproxy/ds_proxy.go b/pkg/api/pluginproxy/ds_proxy.go index a6f75cd9ec0..00ccb0a665c 100644 --- a/pkg/api/pluginproxy/ds_proxy.go +++ b/pkg/api/pluginproxy/ds_proxy.go @@ -300,7 +300,15 @@ func (proxy *DataSourceProxy) validateRequest() error { } // route match - if !strings.HasPrefix(proxy.proxyPath, route.Path) { + r1, err := util.CleanRelativePath(proxy.proxyPath) + if err != nil { + return err + } + r2, err := util.CleanRelativePath(route.Path) + if err != nil { + return err + } + if !strings.HasPrefix(r1, r2) { continue } diff --git a/pkg/api/pluginproxy/ds_proxy_test.go b/pkg/api/pluginproxy/ds_proxy_test.go index 84920f50f52..6b7a113cd12 100644 --- a/pkg/api/pluginproxy/ds_proxy_test.go +++ b/pkg/api/pluginproxy/ds_proxy_test.go @@ -274,6 +274,14 @@ func TestDataSourceProxy_routeRule(t *testing.T) { err = proxy.validateRequest() require.NoError(t, err) }) + + t.Run("path with slashes and user is editor", func(t *testing.T) { + ctx, _ := setUp() + proxy, err := setupDSProxyTest(t, ctx, ds, routes, "//api//admin") + require.NoError(t, err) + err = proxy.validateRequest() + require.Error(t, err) + }) }) t.Run("plugin route with RBAC protection user is allowed", func(t *testing.T) { From 14f46208352529ef1e13f5e1d3c6b4e1f6347208 Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Thu, 24 Apr 2025 15:59:50 -0400 Subject: [PATCH 105/146] Alerting: Optimize clean up rule versions (#102561) * improve removal of old versions Signed-off-by: Yuri Tseretyan * Apply suggestions from code review Co-authored-by: Alexander Akhmetov --------- Signed-off-by: Yuri Tseretyan Co-authored-by: Alexander Akhmetov --- pkg/services/ngalert/store/alert_rule.go | 62 +++++--------- pkg/services/ngalert/store/alert_rule_test.go | 80 ++++++------------- 2 files changed, 44 insertions(+), 98 deletions(-) diff --git a/pkg/services/ngalert/store/alert_rule.go b/pkg/services/ngalert/store/alert_rule.go index 433b417c545..de6a9974ee4 100644 --- a/pkg/services/ngalert/store/alert_rule.go +++ b/pkg/services/ngalert/store/alert_rule.go @@ -453,14 +453,7 @@ func (st DBstore) UpdateAlertRules(ctx context.Context, user *ngmodels.UserUID, if _, err := sess.Insert(&ruleVersions); err != nil { return fmt.Errorf("failed to create new rule versions: %w", err) } - - for _, rule := range ruleVersions { - // delete old versions of alert rule - _, err = st.deleteOldAlertRuleVersions(ctx, rule.RuleUID, rule.RuleOrgID, st.Cfg.RuleVersionRecordLimit) - if err != nil { - st.Logger.Warn("Failed to delete old alert rule versions", "org", rule.RuleOrgID, "rule", rule.RuleUID, "error", err) - } - } + st.deleteOldAlertRuleVersions(ctx, sess, ruleVersions) } if len(keys) > 0 { _ = st.Bus.Publish(ctx, &RuleChangeEvent{ @@ -471,50 +464,31 @@ func (st DBstore) UpdateAlertRules(ctx context.Context, user *ngmodels.UserUID, }) } -func (st DBstore) deleteOldAlertRuleVersions(ctx context.Context, ruleUID string, orgID int64, limit int) (int64, error) { - if limit < 0 { - return 0, fmt.Errorf("failed to delete old alert rule versions: limit is set to '%d' but needs to be > 0", limit) +func (st DBstore) deleteOldAlertRuleVersions(ctx context.Context, sess *db.Session, versions []alertRuleVersion) { + if st.Cfg.RuleVersionRecordLimit < 1 { + return } - - if limit < 1 { - return 0, nil - } - - var affectedRows int64 - err := st.SQLStore.WithDbSession(ctx, func(sess *db.Session) error { - highest := &alertRuleVersion{} - ok, err := sess.Table("alert_rule_version").Desc("id").Where("rule_org_id = ?", orgID).Where("rule_uid = ?", ruleUID).Limit(1, limit).Get(highest) - if err != nil { - return err + logger := st.Logger.FromContext(ctx) + for _, rv := range versions { + deleteTo := rv.Version - int64(st.Cfg.RuleVersionRecordLimit) + // if the last version is less that retention, do nothing + if deleteTo <= 1 { + continue } - if !ok { - // No alert rule versions past the limit exist. Nothing to clean up. - affectedRows = 0 - return nil - } - - res, err := sess.Exec(` - DELETE FROM - alert_rule_version - WHERE - rule_org_id = ? AND rule_uid = ? - AND - id <= ? - `, orgID, ruleUID, highest.ID) + logger := logger.New("org_id", rv.RuleOrgID, "rule_uid", rv.RuleUID, "version", rv.Version, "limit", st.Cfg.RulesPerRuleGroupLimit) + res, err := sess.Exec(`DELETE FROM alert_rule_version WHERE rule_guid = ? AND version <= ?`, rv.RuleGUID, deleteTo) if err != nil { - return err + logger.Error("Failed to delete old alert rule versions", "error", err) + return } rows, err := res.RowsAffected() if err != nil { - return err + rows = -1 } - affectedRows = rows - if affectedRows > 0 { - st.Logger.Info("Deleted old alert_rule_version(s)", "org", orgID, "limit", limit, "delete_count", affectedRows) + if rows != 0 { + logger.Info("Deleted old alert_rule_version(s)", "deleted", rows) } - return nil - }) - return affectedRows, err + } } // preventIntermediateUniqueConstraintViolations prevents unique constraint violations caused by an intermediate update. diff --git a/pkg/services/ngalert/store/alert_rule_test.go b/pkg/services/ngalert/store/alert_rule_test.go index efe5772934d..2c2ffd83ff8 100644 --- a/pkg/services/ngalert/store/alert_rule_test.go +++ b/pkg/services/ngalert/store/alert_rule_test.go @@ -1625,23 +1625,20 @@ func TestIntegration_AlertRuleVersionsCleanup(t *testing.T) { t.Skip("skipping integration test") } usr := models.UserUID("test") - cfg := setting.NewCfg() - cfg.UnifiedAlerting = setting.UnifiedAlertingSettings{ + cfg := setting.UnifiedAlertingSettings{ BaseInterval: time.Duration(rand.Int63n(100)+1) * time.Second, } sqlStore := db.InitTestDB(t) - folderService := setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures()) + folderService := setupFolderService(t, sqlStore, setting.NewCfg(), featuremgmt.WithFeatures()) b := &fakeBus{} - store := createTestStore(sqlStore, folderService, &logtest.Fake{}, cfg.UnifiedAlerting, b) + generator := models.RuleGen - generator = generator.With(generator.WithIntervalMatching(store.Cfg.BaseInterval), generator.WithUniqueOrgID()) + generator = generator.With(generator.WithIntervalMatching(cfg.BaseInterval), generator.WithUniqueOrgID()) t.Run("when calling the cleanup with fewer records than the limit all records should stay", func(t *testing.T) { - alertingCfgSnapshot := cfg.UnifiedAlerting - defer func() { - cfg.UnifiedAlerting = alertingCfgSnapshot - }() - cfg.UnifiedAlerting = setting.UnifiedAlertingSettings{BaseInterval: alertingCfgSnapshot.BaseInterval, RuleVersionRecordLimit: 10} + cfg := setting.UnifiedAlertingSettings{BaseInterval: cfg.BaseInterval, RuleVersionRecordLimit: 10} + store := createTestStore(sqlStore, folderService, &logtest.Fake{}, cfg, b) + rule := createRule(t, store, generator) firstNewRule := models.CopyRule(rule) firstNewRule.Title = util.GenerateShortUID() @@ -1685,43 +1682,27 @@ func TestIntegration_AlertRuleVersionsCleanup(t *testing.T) { }) t.Run("only oldest records surpassing the limit should be deleted", func(t *testing.T) { - alertingCfgSnapshot := cfg.UnifiedAlerting - defer func() { - cfg.UnifiedAlerting = alertingCfgSnapshot - }() - cfg.UnifiedAlerting = setting.UnifiedAlertingSettings{BaseInterval: alertingCfgSnapshot.BaseInterval, RuleVersionRecordLimit: 1} + cfg := setting.UnifiedAlertingSettings{BaseInterval: cfg.BaseInterval, RuleVersionRecordLimit: 2} + store := createTestStore(sqlStore, folderService, &logtest.Fake{}, cfg, b) rule := createRule(t, store, generator) - oldRule := models.CopyRule(rule) - oldRule.Title = "old-record" - err := store.UpdateAlertRules(context.Background(), &usr, []models.UpdateRule{{ - Existing: rule, - New: *oldRule, - }}) // first entry in `rule_version_history` table happens here - require.NoError(t, err) - rule.Version = rule.Version + 1 - middleRule := models.CopyRule(rule) - middleRule.Title = "middle-record" - err = store.UpdateAlertRules(context.Background(), &usr, []models.UpdateRule{{ - Existing: rule, - New: *middleRule, - }}) // second entry in `rule_version_history` table happens here - require.NoError(t, err) + for i := 0; i < 4; i++ { + r, err := store.GetAlertRuleByUID(context.Background(), &models.GetAlertRuleByUIDQuery{UID: rule.UID}) + require.NoError(t, err) + rn := models.CopyRule(r) + rn.Title = util.GenerateShortUID() + err = store.UpdateAlertRules(context.Background(), &models.AlertingUserUID, []models.UpdateRule{ + { + Existing: r, + New: *rn, + }, + }) + require.NoError(t, err) + } - rule.Version = rule.Version + 1 - newerRule := models.CopyRule(rule) - newerRule.Title = "newer-record" - err = store.UpdateAlertRules(context.Background(), &usr, []models.UpdateRule{{ - Existing: rule, - New: *newerRule, - }}) // second entry in `rule_version_history` table happens here + rule, err := store.GetAlertRuleByUID(context.Background(), &models.GetAlertRuleByUIDQuery{UID: rule.UID}) require.NoError(t, err) - // only the `old-record` should be deleted since limit is set to 1 and there are total 2 records - rowsAffected, err := store.deleteOldAlertRuleVersions(context.Background(), rule.UID, rule.OrgID, 1) - require.NoError(t, err) - require.Equal(t, int64(2), rowsAffected) - err = sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error { var alertRuleVersions []*alertRuleVersion err := sess.Table(alertRuleVersion{}).Desc("id").Where("rule_org_id = ? and rule_uid = ?", rule.OrgID, rule.UID).Find(&alertRuleVersions) @@ -1729,22 +1710,13 @@ func TestIntegration_AlertRuleVersionsCleanup(t *testing.T) { return err } require.NoError(t, err) - assert.Len(t, alertRuleVersions, 1) - assert.Equal(t, "newer-record", alertRuleVersions[0].Title) + assert.Len(t, alertRuleVersions, 2) + assert.Equal(t, rule.Title, alertRuleVersions[0].Title) + assert.Equal(t, rule.Version, alertRuleVersions[0].Version) return err }) require.NoError(t, err) }) - - t.Run("limit set to 0 should not fail", func(t *testing.T) { - count, err := store.deleteOldAlertRuleVersions(context.Background(), "", 1, 0) - require.NoError(t, err) - require.Equal(t, int64(0), count) - }) - t.Run("limit set to negative should fail", func(t *testing.T) { - _, err := store.deleteOldAlertRuleVersions(context.Background(), "", 1, -1) - require.Error(t, err) - }) } func TestIntegration_ListAlertRules(t *testing.T) { From 7ed17cacbfb5c222e05da126abd3d22f96f82c97 Mon Sep 17 00:00:00 2001 From: Scott Lepper Date: Thu, 24 Apr 2025 17:35:35 -0400 Subject: [PATCH 106/146] Scenes: validate time zone (#104481) * Scenes: validate time zone --- package.json | 4 ++-- yarn.lock | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 264ca1c8d5b..a7ef1898773 100644 --- a/package.json +++ b/package.json @@ -275,8 +275,8 @@ "@grafana/plugin-ui": "0.10.5", "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "6.10.0", - "@grafana/scenes-react": "6.10.0", + "@grafana/scenes": "6.10.2", + "@grafana/scenes-react": "6.10.2", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/yarn.lock b/yarn.lock index e674f1d8620..a16603a5027 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3555,11 +3555,11 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes-react@npm:6.10.0": - version: 6.10.0 - resolution: "@grafana/scenes-react@npm:6.10.0" +"@grafana/scenes-react@npm:6.10.2": + version: 6.10.2 + resolution: "@grafana/scenes-react@npm:6.10.2" dependencies: - "@grafana/scenes": "npm:6.10.0" + "@grafana/scenes": "npm:6.10.2" lru-cache: "npm:^10.2.2" react-use: "npm:^17.4.0" peerDependencies: @@ -3571,13 +3571,13 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/559de67feb4005173bc4a08c0c968a32e2e88fc362192825bdae0e5f3e83cf5e78bb4c2a373c66ac2f38a90333622e6623e372989910baf48121135d032291f1 + checksum: 10/f87d51654becdbb9c703b04d60532413ccb3d963e6addb6577d4fe4b96a99d52e792cd12c6fed102b2a01c07266cac29061d1f38cb43e5929ea892c6093ea671 languageName: node linkType: hard -"@grafana/scenes@npm:6.10.0": - version: 6.10.0 - resolution: "@grafana/scenes@npm:6.10.0" +"@grafana/scenes@npm:6.10.2": + version: 6.10.2 + resolution: "@grafana/scenes@npm:6.10.2" dependencies: "@floating-ui/react": "npm:^0.26.16" "@leeoniya/ufuzzy": "npm:^1.0.16" @@ -3595,7 +3595,7 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/4f89f59374f6bd20fcd0afec7d236e4e1754f43a379348e704c0021952b241ece2d04134de8d668bc4d4fa286ebcf1c7874403c70004e85b1330bd5f76bb5423 + checksum: 10/4a6acc3e3f2ceb3316fcdb8cba79d54b3fca0130f05d480349bfc726ca6624cba6acf2333d96a292cc1a573f578274b1e85b7ba7c0096deda3fb02feb0179e3d languageName: node linkType: hard @@ -17583,8 +17583,8 @@ __metadata: "@grafana/plugin-ui": "npm:0.10.5" "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" - "@grafana/scenes": "npm:6.10.0" - "@grafana/scenes-react": "npm:6.10.0" + "@grafana/scenes": "npm:6.10.2" + "@grafana/scenes-react": "npm:6.10.2" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/tsconfig": "npm:^2.0.0" From 618ffd02757f576941d285c8c8418f4b894d6877 Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Fri, 25 Apr 2025 09:42:37 +0300 Subject: [PATCH 107/146] API clients: Add generator (#104093) * Add API client generator * Extract config entry template * Fix index file * Fix message and file pattern * Fix generate-rtk template * Match generated-api * Format * Split helpers * Cleanup * Remove unused helpers * Simplify group name handling * Run generate-apis * Prettier * Format + lint * improve lint/format * Optional filterEndpoints * Format * Update readme * More updates * Move the helpers out * Switch to TS * Cleanup types * Add support for Enterprise * Add comments * Refactor endpoint handling and update README * Simplify checks * Do not register reducers and middleware for enterprise * More docs updates * Remove redundant sections * Format gitignored files * Add limitations * Simplify types * Simplify path logic * Do not format OSS paths for enterprise * dedupe * format * Simplify instructions * Update lockfile * Add comments * Remove custom types --- package.json | 4 +- public/app/api/README.md | 136 +--- public/app/api/generator/README.md | 52 ++ public/app/api/generator/helpers.ts | 115 +++ public/app/api/generator/plopfile.ts | 165 ++++ .../api/generator/templates/baseAPI.ts.hbs | 14 + .../api/generator/templates/config-entry.hbs | 9 + .../app/api/generator/templates/index.ts.hbs | 3 + public/app/api/generator/types.ts | 27 + public/app/core/reducers/root.ts | 4 + public/app/store/configureStore.ts | 4 + scripts/generate-rtk-apis.ts | 1 + yarn.lock | 760 +++++++++++++++++- 13 files changed, 1136 insertions(+), 158 deletions(-) create mode 100644 public/app/api/generator/README.md create mode 100644 public/app/api/generator/helpers.ts create mode 100644 public/app/api/generator/plopfile.ts create mode 100644 public/app/api/generator/templates/baseAPI.ts.hbs create mode 100644 public/app/api/generator/templates/config-entry.hbs create mode 100644 public/app/api/generator/templates/index.ts.hbs create mode 100644 public/app/api/generator/types.ts diff --git a/package.json b/package.json index a7ef1898773..6206f07ec1f 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,8 @@ "plugin:build:commit": "nx run-many -t build:commit --projects='tag:scope:plugin'", "plugin:build:dev": "nx run-many -t dev --projects='tag:scope:plugin' --maxParallel=100", "process-specs": "node --experimental-strip-types scripts/process-specs.ts", - "generate-apis": "yarn process-specs && rtk-query-codegen-openapi ./scripts/generate-rtk-apis.ts" + "generate-apis": "yarn process-specs && rtk-query-codegen-openapi ./scripts/generate-rtk-apis.ts", + "generate:api-client": "NODE_OPTIONS='--experimental-strip-types' plop --plopfile public/app/api/generator/plopfile.ts" }, "grafana": { "whatsNewUrl": "https://grafana.com/docs/grafana/next/whatsnew/whats-new-in-v%[1]s-%[2]s/", @@ -216,6 +217,7 @@ "nx": "20.7.1", "openapi-types": "^12.1.3", "pdf-parse": "^1.1.1", + "plop": "^4.0.1", "postcss": "8.5.1", "postcss-loader": "8.1.1", "postcss-reporter": "7.1.0", diff --git a/public/app/api/README.md b/public/app/api/README.md index eafebb5e614..67bf87ae19a 100644 --- a/public/app/api/README.md +++ b/public/app/api/README.md @@ -7,7 +7,7 @@ To show the steps to follow, we are going to work on adding an API client to cre First, check if the `group` and the `version` are already present in [openapi_test.go](/pkg/tests/apis/openapi_test.go). If so, move on to the next step.
If you need to add a new block, you can check for the right `group` and `version` in the backend API call that you want to replicate in the frontend. -```jsx +```go { Group: "dashboard.grafana.app", Version: "v0alpha1", @@ -20,136 +20,6 @@ Afterwards, you need to run the `TestIntegrationOpenAPIs` test. Note that it wil > Note: You don’t need to follow these two steps if the `group` you’re working with is already in the `openapi_test.go` file. -
+### 2. Run the API generator script -### 2. Create the API definition - -In the [`/public/app/api/clients`](/public/app/api/clients) folder, create a new folder and `baseAPI.ts` file for your group. This file should have the following content: - -```jsx -import { createApi } from '@reduxjs/toolkit/query/react'; - -import { createBaseQuery } from 'app/api/createBaseQuery'; -import { getAPIBaseURL } from 'app/api/utils'; - -export const BASE_URL = getAPIBaseURL('dashboard.grafana.app', 'v0alpha1'); - -export const api = createApi({ - reducerPath: 'dashboardAPI', - baseQuery: createBaseQuery({ - baseURL: BASE_URL, - }), - endpoints: () => ({}), -}); -``` - -This is the API definition for the specific group you're working with, where `getAPIBaseURL` should have the proper `group` and `version` as parameters. The `reducerPath` needs to be unique. The convention is to use `API`: `dashboard` will be `dashboardAPI`, `iam` will be `iamAPI` and so on. - -### 3. Add your new client to the generation script - -Open [generate-rtk-apis.ts](/scripts/generate-rtk-apis.ts) and add the following information: - -| Data | Descritpion | -| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| outputFile name | File that will be created after running the API Client Generation script. It is the key of the object. | -| apiFile | File with the group's API definition. | -| schemaFile | File with the schema that was automatically created in the second step. Although it is in openapi_snapshots, you should link the one saved in `data/openapi`. | -| filterEndpoints | The `operationId` of the particular route you want to work with. You can check the available operationIds in the specific group's spec file. As seen in the `migrate-to-cloud` one, it is an array | -|  tag | Must be set to `true`, to automatically attach tags to endpoints. This is needed for proper cache invalidation. See more info in the [official documentation](https://redux-toolkit.js.org/rtk-query/usage/automated-refetching#:~:text=RTK%20Query%20uses,an%20active%20subscription.).  | - -
- -> More info in [Redux Toolkit](https://redux-toolkit.js.org/rtk-query/usage/code-generation#simple-usage) - -In our example, the information added will be: - -```jsx -'../public/app/api/clients/dashboard/endpoints.gen.ts': { - apiFile: '../public/app/api/clients/dashboard/baseAPI.ts', - schemaFile: '../data/openapi/dashboard.grafana.app-v0alpha1.json', - filterEndpoints: ['createDashboard', 'updateDashboard'], - tag: true, -}, -``` - -### 4. Run the API client generation script - -Then, we are ready to run the script to create the API client: - -```jsx -yarn generate-apis -``` - -This will create an `endpoints.gen.ts` file in the path specified in the previous step. - -### 5. Create the index file for your hooks - -In the same `api` folder where the `endpoints.gen.ts` file has been saved, you have to create an index file from which you can import the types and hooks needed. By doing this, we selectively export hooks/types from `endpoints.gen.ts`. - -In our case, the dashboard index will be like: - -```jsx -import { generatedAPI } from './endpoints.gen'; - -export const dashboardAPI = generatedAPI; -export const { useCreateDashboardMutation, useUpdateDashboardMutation} = dashboardAPI; -// eslint-disable-next-line no-barrel-files/no-barrel-files -export { type Dashboard } from './endpoints.gen'; - -``` - -There are some use cases where the hook will not work out of the box, and that is a clue to see if it needs to be modified. The hooks can be tweaked by using `enhanceEndpoints`. - -```jsx -export const dashboardsAPI = generatedApi.enhanceEndpoints({ - endpoints: { - // Need to mutate the generated query to set the Content-Type header correctly - updateDashboard: (endpointDefinition) => { - const originalQuery = endpointDefinition.query; - if (originalQuery) { - endpointDefinition.query = (requestOptions) => ({ - ...originalQuery(requestOptions), - headers: { - 'Content-Type': 'application/merge-patch+json', - }, - }); - } - }, - }, -}); -``` - -### 6. Add reducers and middleware to the Redux store - -Last but not least, you need to add the middleware and reducers to the store. - -In Grafana, the reducers are added to [`root.ts`](/public/app/core/reducers/root.ts): - -```jsx - import { dashboardAPI } from ''; - const rootReducers = { - ..., - [dashboardAPI.reducerPath]: dashboardAPI.reducer, - }; -``` - -And the middleware is added to [`configureStore.ts`](/public/app/store/configureStore.ts): - -```jsx -import { dashboardAPI } from ''; -export function configureStore(initialState?: Partial) { - const store = reduxConfigureStore({ - reducer: createRootReducer(), - middleware: (getDefaultMiddleware) => - getDefaultMiddleware({ thunk: true, serializableCheck: false, immutableCheck: false }).concat( - ..., - dashboardAPI.middleware - ), - ..., - }); -``` - -You have available the official documentation in [RTK Query](https://redux-toolkit.js.org/tutorials/rtk-query#add-the-service-to-your-store) - -After this step is done, it is time to use your hooks across Grafana. -Enjoy coding! +Run `yarn generate:api-client` and follow the prompts. See [API Client Generator](./generator/README.md) for details. diff --git a/public/app/api/generator/README.md b/public/app/api/generator/README.md new file mode 100644 index 00000000000..fb54b8011d4 --- /dev/null +++ b/public/app/api/generator/README.md @@ -0,0 +1,52 @@ +# RTK Query API Client Generator + +This generator automates the process of creating RTK Query API clients for Grafana's API groups. It replaces the manual steps outlined in the [main API documentation](../README.md). + +## Usage + +```bash +yarn generate:api-client +``` + +The CLI will prompt for: + +1. **Enterprise or OSS API** - Whether this is an Enterprise or OSS API. This affects paths and build commands. +2. **API group name** - The basic name for the API (e.g., `dashboard`) +3. **API group** - The full API group name (defaults to `.grafana.app`) +4. **API version** - The API version (e.g., `v0alpha1`) +5. **Reducer path** - The Redux reducer path (defaults to `API`). This will also be used as the API's named export. +6. **Endpoints** - Optional comma-separated list of endpoints to include (e.g., `createDashboard,updateDashboard`). If not provided, all endpoints will be included. + +## What It Does + +The generator automates the following: + +1. Creates the `baseAPI.ts` file for the API group +2. Updates the appropriate generate script to include the API client + - `scripts/generate-rtk-apis.ts` for OSS APIs + - `local/generate-enterprise-apis.ts` for Enterprise APIs +3. Creates the `index.ts` file with proper exports +4. For OSS APIs only: Registers Redux reducers and middleware in the store. For Enterprise this needs to be done manually +5. Formats all generated files using Prettier and ESLint +6. Automatically runs the appropriate command to generate endpoints from the OpenAPI schema + +## Limitations + +- The generator is optimized for Kubernetes-style APIs, as it requires Kubernetes resource details. For legacy APIs, manual adjustments may be needed. +- It expects processed OpenAPI specifications to exist in the `openapi_snapshots` directory + +## Troubleshooting + +### Missing OpenAPI Schema + +If an error about a missing OpenAPI schema appears, check that: + +1. The API group and version exist in the backend +2. The `TestIntegrationOpenAPIs` test has been run to generate the schema (step 1 in the [main API documentation](../README.md)). +3. The schema file exists at `data/openapi/-.json` + +### Validation Errors + +- API group must include `.grafana.app` +- Version must be in format `v0alpha1`, `v1beta2`, etc. +- Reducer path must end with `API` diff --git a/public/app/api/generator/helpers.ts b/public/app/api/generator/helpers.ts new file mode 100644 index 00000000000..71b7447a11f --- /dev/null +++ b/public/app/api/generator/helpers.ts @@ -0,0 +1,115 @@ +import { execSync } from 'child_process'; +import path from 'path'; + +type PlopActionFunction = ( + answers: Record, + config?: Record +) => string | Promise; + +// Helper to remove quotes from operation IDs +export const removeQuotes = (str: string | unknown) => { + if (typeof str !== 'string') { + return str; + } + return str.replace(/^['"](.*)['"]$/, '$1'); +}; + +export const formatEndpoints = () => (endpointsInput: string | string[]) => { + if (Array.isArray(endpointsInput)) { + return endpointsInput.map((op) => `'${removeQuotes(op)}'`).join(', '); + } + + // Handle string input (comma-separated) + if (typeof endpointsInput === 'string') { + const endpointsArray = endpointsInput + .split(',') + .map((id) => id.trim()) + .filter(Boolean); + + return endpointsArray.map((op) => `'${removeQuotes(op)}'`).join(', '); + } + + return ''; +}; + +// List of created or modified files +export const getFilesToFormat = (groupName: string, isEnterprise = false) => { + const apiClientBasePath = isEnterprise ? 'public/app/extensions/api/clients' : 'public/app/api/clients'; + const generateScriptPath = isEnterprise ? 'local/generate-enterprise-apis.ts' : 'scripts/generate-rtk-apis.ts'; + + return [ + `${apiClientBasePath}/${groupName}/baseAPI.ts`, + `${apiClientBasePath}/${groupName}/index.ts`, + generateScriptPath, + ...(isEnterprise ? [] : [`public/app/core/reducers/root.ts`, `public/app/store/configureStore.ts`]), + ]; +}; + +export const runGenerateApis = + (basePath: string): PlopActionFunction => + (answers, config) => { + try { + const isEnterprise = answers.isEnterprise || (config && config.isEnterprise); + + let command; + if (isEnterprise) { + command = 'yarn process-specs && npx rtk-query-codegen-openapi ./local/generate-enterprise-apis.ts'; + } else { + command = 'yarn generate-apis'; + } + + console.log(`⏳ Running ${command} to generate endpoints...`); + execSync(command, { stdio: 'inherit', cwd: basePath }); + return '✅ API endpoints generated successfully!'; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + console.error('❌ Failed to generate API endpoints:', errorMessage); + return '❌ Failed to generate API endpoints. See error above.'; + } + }; + +export const formatFiles = + (basePath: string): PlopActionFunction => + (_, config) => { + if (!config || !Array.isArray(config.files)) { + console.error('Invalid config passed to formatFiles action'); + return '❌ Formatting failed: Invalid configuration'; + } + + const filesToFormat = config.files.map((file: string) => path.join(basePath, file)); + + try { + const filesList = filesToFormat.map((file: string) => `"${file}"`).join(' '); + + console.log('🧹 Running ESLint on generated/modified files...'); + try { + execSync(`yarn eslint --fix ${filesList}`, { cwd: basePath }); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + console.warn(`⚠️ Warning: ESLint encountered issues: ${errorMessage}`); + } + + console.log('🧹 Running Prettier on generated/modified files...'); + try { + // '--ignore-path' is necessary so the gitignored files ('local/' folder) can still be formatted + execSync(`yarn prettier --write ${filesList} --ignore-path=./.prettierignore`, { cwd: basePath }); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + console.warn(`⚠️ Warning: Prettier encountered issues: ${errorMessage}`); + } + + return '✅ Files linted and formatted successfully!'; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + console.error('⚠️ Warning: Formatting operations failed:', errorMessage); + return '⚠️ Warning: Formatting operations failed.'; + } + }; + +export const validateGroup = (group: string) => { + return group && group.includes('.grafana.app') ? true : 'Group should be in format: name.grafana.app'; +}; + +export const validateVersion = (version: string) => { + return version && /^v\d+[a-z]*\d+$/.test(version) ? true : 'Version should be in format: v0alpha1, v1beta2, etc.'; +}; diff --git a/public/app/api/generator/plopfile.ts b/public/app/api/generator/plopfile.ts new file mode 100644 index 00000000000..921089c8c5a --- /dev/null +++ b/public/app/api/generator/plopfile.ts @@ -0,0 +1,165 @@ +import path from 'path'; +import type { NodePlopAPI, PlopGeneratorConfig } from 'plop'; + +import { + formatEndpoints, + validateGroup, + validateVersion, + getFilesToFormat, + runGenerateApis, + formatFiles, + // The file extension is necessary to make the imports + // work with the '--experimental-strip-types' flag + // @ts-ignore +} from './helpers.ts'; +// @ts-ignore +import { type ActionConfig, type PlopData, isPlopData } from './types.ts'; + +export default function plopGenerator(plop: NodePlopAPI) { + // Grafana root path + const basePath = path.resolve(import.meta.dirname, '../../../..'); + + // Register custom action types + plop.setActionType('runGenerateApis', runGenerateApis(basePath)); + plop.setActionType('formatFiles', formatFiles(basePath)); + + // Used in templates to format endpoints + plop.setHelper('formatEndpoints', formatEndpoints()); + + const generateRtkApiActions = (data: PlopData) => { + const { reducerPath, groupName, isEnterprise } = data; + + const apiClientBasePath = isEnterprise ? 'public/app/extensions/api/clients' : 'public/app/api/clients'; + const generateScriptPath = isEnterprise ? 'local/generate-enterprise-apis.ts' : 'scripts/generate-rtk-apis.ts'; + + // Using app path, so the imports work on any file level + const clientImportPath = isEnterprise ? '../extensions/api/clients' : 'app/api/clients'; + + const apiPathPrefix = isEnterprise ? '../public/app/extensions/api/clients' : '../public/app/api/clients'; + + const templateData = { + ...data, + apiPathPrefix, + }; + + // Base actions that are always added + const actions: ActionConfig[] = [ + { + type: 'add', + path: path.join(basePath, `${apiClientBasePath}/${groupName}/baseAPI.ts`), + templateFile: './templates/baseAPI.ts.hbs', + }, + { + type: 'modify', + path: path.join(basePath, generateScriptPath), + pattern: '// PLOP_INJECT_API_CLIENT - Used by the API client generator', + templateFile: './templates/config-entry.hbs', + data: templateData, + }, + { + type: 'add', + path: path.join(basePath, `${apiClientBasePath}/${groupName}/index.ts`), + templateFile: './templates/index.ts.hbs', + }, + ]; + + // Only add redux reducer and middleware for OSS clients + if (!isEnterprise) { + actions.push( + { + type: 'modify', + path: path.join(basePath, 'public/app/core/reducers/root.ts'), + pattern: '// PLOP_INJECT_IMPORT', + template: `import { ${reducerPath} } from '${clientImportPath}/${groupName}';\n// PLOP_INJECT_IMPORT`, + }, + { + type: 'modify', + path: path.join(basePath, 'public/app/core/reducers/root.ts'), + pattern: '// PLOP_INJECT_REDUCER', + template: `[${reducerPath}.reducerPath]: ${reducerPath}.reducer,\n // PLOP_INJECT_REDUCER`, + }, + { + type: 'modify', + path: path.join(basePath, 'public/app/store/configureStore.ts'), + pattern: '// PLOP_INJECT_IMPORT', + template: `import { ${reducerPath} } from '${clientImportPath}/${groupName}';\n// PLOP_INJECT_IMPORT`, + }, + { + type: 'modify', + path: path.join(basePath, 'public/app/store/configureStore.ts'), + pattern: '// PLOP_INJECT_MIDDLEWARE', + template: `${reducerPath}.middleware,\n // PLOP_INJECT_MIDDLEWARE`, + } + ); + } + + // Add formatting and generation actions + actions.push( + { + type: 'formatFiles', + files: getFilesToFormat(groupName, isEnterprise), + }, + { + type: 'runGenerateApis', + isEnterprise, + } + ); + + return actions; + }; + + const generator: PlopGeneratorConfig = { + description: 'Generate RTK Query API client for a Grafana API group', + prompts: [ + { + type: 'confirm', + name: 'isEnterprise', + message: 'Is this a Grafana Enterprise API?', + default: false, + }, + { + type: 'input', + name: 'groupName', + message: 'API group name (e.g. dashboard):', + validate: (input: string) => (input?.trim() ? true : 'Group name is required'), + }, + { + type: 'input', + name: 'group', + message: 'API group (e.g. dashboard.grafana.app):', + default: (answers: { groupName?: string }) => `${answers.groupName}.grafana.app`, + validate: validateGroup, + }, + { + type: 'input', + name: 'version', + message: 'API version (e.g. v0alpha1):', + default: 'v0alpha1', + validate: validateVersion, + }, + { + type: 'input', + name: 'reducerPath', + message: 'Reducer path (e.g. dashboardAPI):', + default: (answers: { groupName?: string }) => `${answers.groupName}API`, + validate: (input: string) => + input?.endsWith('API') ? true : 'Reducer path should end with "API" (e.g. dashboardAPI)', + }, + { + type: 'input', + name: 'endpoints', + message: 'Endpoints to include (comma-separated, optional):', + validate: () => true, + }, + ], + actions: function (data) { + if (!isPlopData(data)) { + throw new Error('Invalid data format received from prompts'); + } + + return generateRtkApiActions(data); + }, + }; + + plop.setGenerator('rtk-api-client', generator); +} diff --git a/public/app/api/generator/templates/baseAPI.ts.hbs b/public/app/api/generator/templates/baseAPI.ts.hbs new file mode 100644 index 00000000000..a5fd29cf488 --- /dev/null +++ b/public/app/api/generator/templates/baseAPI.ts.hbs @@ -0,0 +1,14 @@ +import { createApi } from '@reduxjs/toolkit/query/react'; + +import { createBaseQuery } from 'app/api/createBaseQuery'; +import { getAPIBaseURL } from 'app/api/utils'; + +export const BASE_URL = getAPIBaseURL('{{group}}', '{{version}}'); + +export const api = createApi({ + reducerPath: '{{reducerPath}}', + baseQuery: createBaseQuery({ + baseURL: BASE_URL, + }), + endpoints: () => ({}), +}); diff --git a/public/app/api/generator/templates/config-entry.hbs b/public/app/api/generator/templates/config-entry.hbs new file mode 100644 index 00000000000..53c0eaf673e --- /dev/null +++ b/public/app/api/generator/templates/config-entry.hbs @@ -0,0 +1,9 @@ +'{{apiPathPrefix}}/{{groupName}}/endpoints.gen.ts': { + apiFile: '{{apiPathPrefix}}/{{groupName}}/baseAPI.ts', + schemaFile: '../data/openapi/{{group}}-{{version}}.json', + {{#if endpoints}} + filterEndpoints: [{{{formatEndpoints endpoints}}}], + {{/if}} + tag: true, +}, +// PLOP_INJECT_API_CLIENT - Used by the API client generator diff --git a/public/app/api/generator/templates/index.ts.hbs b/public/app/api/generator/templates/index.ts.hbs new file mode 100644 index 00000000000..de9c3c66cf8 --- /dev/null +++ b/public/app/api/generator/templates/index.ts.hbs @@ -0,0 +1,3 @@ +import { generatedAPI } from './endpoints.gen'; + +export const {{reducerPath}} = generatedAPI.enhanceEndpoints({}); diff --git a/public/app/api/generator/types.ts b/public/app/api/generator/types.ts new file mode 100644 index 00000000000..0eb73b6ab90 --- /dev/null +++ b/public/app/api/generator/types.ts @@ -0,0 +1,27 @@ +import type { AddActionConfig, ModifyActionConfig } from 'plop'; + +export interface FormatFilesActionConfig { + type: 'formatFiles'; + files: string[]; +} + +export interface RunGenerateApisActionConfig { + type: 'runGenerateApis'; + isEnterprise: boolean; +} + +// Union type of all possible action configs +export type ActionConfig = AddActionConfig | ModifyActionConfig | FormatFilesActionConfig | RunGenerateApisActionConfig; + +export interface PlopData { + groupName: string; + group: string; + version: string; + reducerPath: string; + endpoints: string; + isEnterprise: boolean; +} + +export function isPlopData(data: unknown): data is PlopData { + return typeof data === 'object' && data !== null; +} diff --git a/public/app/core/reducers/root.ts b/public/app/core/reducers/root.ts index 6b307d25529..99877d65c90 100644 --- a/public/app/core/reducers/root.ts +++ b/public/app/core/reducers/root.ts @@ -35,6 +35,8 @@ import { provisioningAPI } from '../../api/clients/provisioning'; import { alertingApi } from '../../features/alerting/unified/api/alertingApi'; import { userPreferencesAPI } from '../../features/preferences/api'; import { cleanUpAction } from '../actions/cleanUp'; +// Used by the API client generator +// PLOP_INJECT_IMPORT const rootReducers = { ...sharedReducers, @@ -69,6 +71,8 @@ const rootReducers = { [provisioningAPI.reducerPath]: provisioningAPI.reducer, [folderAPI.reducerPath]: folderAPI.reducer, [advisorAPI.reducerPath]: advisorAPI.reducer, + // PLOP_INJECT_REDUCER + // Used by the API client generator }; const addedReducers = {}; diff --git a/public/app/store/configureStore.ts b/public/app/store/configureStore.ts index a5061d7a9ed..b161a695acf 100644 --- a/public/app/store/configureStore.ts +++ b/public/app/store/configureStore.ts @@ -13,6 +13,8 @@ import { folderAPI } from '../api/clients/folder'; import { iamAPI } from '../api/clients/iam'; import { playlistAPI } from '../api/clients/playlist'; import { provisioningAPI } from '../api/clients/provisioning'; +// Used by the API client generator +// PLOP_INJECT_IMPORT import { buildInitialState } from '../core/reducers/navModel'; import { addReducer, createRootReducer } from '../core/reducers/root'; import { alertingApi } from '../features/alerting/unified/api/alertingApi'; @@ -49,6 +51,8 @@ export function configureStore(initialState?: Partial) { provisioningAPI.middleware, folderAPI.middleware, advisorAPI.middleware, + // PLOP_INJECT_MIDDLEWARE + // Used by the API client generator ...extraMiddleware ), devTools: process.env.NODE_ENV !== 'production', diff --git a/scripts/generate-rtk-apis.ts b/scripts/generate-rtk-apis.ts index 8375026e378..2b805713919 100644 --- a/scripts/generate-rtk-apis.ts +++ b/scripts/generate-rtk-apis.ts @@ -72,6 +72,7 @@ const config: ConfigFile = { filterEndpoints: ['listPlaylist', 'getPlaylist', 'createPlaylist', 'deletePlaylist', 'replacePlaylist'], tag: true, }, + // PLOP_INJECT_API_CLIENT - Used by the API client generator }, }; diff --git a/yarn.lock b/yarn.lock index a16603a5027..e578eb3f212 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3916,10 +3916,10 @@ __metadata: languageName: node linkType: hard -"@inquirer/figures@npm:^1.0.8": - version: 1.0.8 - resolution: "@inquirer/figures@npm:1.0.8" - checksum: 10/0e5e4fbb15e799e818c598fcc3558ef076daf78662149711b046723fd6316381e95f7d5573d6ef0062095ad22c6ac98833033f0948df5c722932107a567fd9c3 +"@inquirer/figures@npm:^1.0.3, @inquirer/figures@npm:^1.0.8": + version: 1.0.11 + resolution: "@inquirer/figures@npm:1.0.11" + checksum: 10/357ddd2e83718bc3c9189d518b93fd69099af9c860354df9a5ac0ec024cb5df1228ae4608d2de7625624d2adcd047db813f29426a610eaae7b9e449f8c753c6b languageName: node linkType: hard @@ -9353,6 +9353,13 @@ __metadata: languageName: node linkType: hard +"@types/fined@npm:*": + version: 1.1.5 + resolution: "@types/fined@npm:1.1.5" + checksum: 10/7a9e58904ac95205a989046dbfb3f5c91f5f07664d8d3e4b2d4e25777e5478252d8cea9637c3dc215526a0d2fb3ab3681047183d3447af00ce934466f6569f56 + languageName: node + linkType: hard + "@types/fs-extra@npm:^11.0.4": version: 11.0.4 resolution: "@types/fs-extra@npm:11.0.4" @@ -9478,6 +9485,16 @@ __metadata: languageName: node linkType: hard +"@types/inquirer@npm:^9.0.3": + version: 9.0.7 + resolution: "@types/inquirer@npm:9.0.7" + dependencies: + "@types/through": "npm:*" + rxjs: "npm:^7.2.0" + checksum: 10/84cefdd10d7ca747ae2338ea35518020abbc28f7670ade446e367c4cd333153618b374d30830253f31a9567dd26e7f3093fb3cd20210af5ba27a1a91b89bb97e + languageName: node + linkType: hard + "@types/is-hotkey@npm:0.1.10": version: 0.1.10 resolution: "@types/is-hotkey@npm:0.1.10" @@ -9584,6 +9601,16 @@ __metadata: languageName: node linkType: hard +"@types/liftoff@npm:^4.0.3": + version: 4.0.3 + resolution: "@types/liftoff@npm:4.0.3" + dependencies: + "@types/fined": "npm:*" + "@types/node": "npm:*" + checksum: 10/ee38489d296f14caef85fdeb9a7de45232e0c221bdcdd97ccae3f720c2d12315377198790c228d569e64eed26b19ae132fa119b7e675b552a69af3b8538807f3 + languageName: node + linkType: hard + "@types/lodash.memoize@npm:^4.1.7": version: 4.1.7 resolution: "@types/lodash.memoize@npm:4.1.7" @@ -10140,6 +10167,15 @@ __metadata: languageName: node linkType: hard +"@types/through@npm:*": + version: 0.0.33 + resolution: "@types/through@npm:0.0.33" + dependencies: + "@types/node": "npm:*" + checksum: 10/fd0b73f873a64ed5366d1d757c42e5dbbb2201002667c8958eda7ca02fff09d73de91360572db465ee00240c32d50c6039ea736d8eca374300f9664f93e8da39 + languageName: node + linkType: hard + "@types/tinycolor2@npm:1.4.6": version: 1.4.6 resolution: "@types/tinycolor2@npm:1.4.6" @@ -11045,6 +11081,16 @@ __metadata: languageName: node linkType: hard +"aggregate-error@npm:^4.0.0": + version: 4.0.1 + resolution: "aggregate-error@npm:4.0.1" + dependencies: + clean-stack: "npm:^4.0.0" + indent-string: "npm:^5.0.0" + checksum: 10/bb3ffdfd13447800fff237c2cba752c59868ee669104bb995dfbbe0b8320e967d679e683dabb640feb32e4882d60258165cde0baafc4cd467cc7d275a13ad6b5 + languageName: node + linkType: hard + "ajv-draft-04@npm:^1.0.0": version: 1.0.0 resolution: "ajv-draft-04@npm:1.0.0" @@ -11343,6 +11389,13 @@ __metadata: languageName: node linkType: hard +"array-each@npm:^1.0.1": + version: 1.0.1 + resolution: "array-each@npm:1.0.1" + checksum: 10/eb2393c1200003993d97dab2b280aa01e6ca339b383198e5d250cc8cd31f8012a0c22b66f275401a80e89e21bfab420e0f4c77c295637dea525fe0e152ba2300 + languageName: node + linkType: hard + "array-flatten@npm:1.1.1": version: 1.1.1 resolution: "array-flatten@npm:1.1.1" @@ -11371,6 +11424,13 @@ __metadata: languageName: node linkType: hard +"array-slice@npm:^1.0.0": + version: 1.1.0 + resolution: "array-slice@npm:1.1.0" + checksum: 10/3c8ecc7eefe104c97e2207e1d5644be160924c89e08b1807f3cad77f4a8fb10150fc275ebfab90dc02064d178b010cad31b69c9386769d172da270be5e233c51 + languageName: node + linkType: hard + "array-tree-filter@npm:^2.1.0": version: 2.1.0 resolution: "array-tree-filter@npm:2.1.0" @@ -12411,6 +12471,17 @@ __metadata: languageName: node linkType: hard +"capital-case@npm:^1.0.4": + version: 1.0.4 + resolution: "capital-case@npm:1.0.4" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + upper-case-first: "npm:^2.0.2" + checksum: 10/41fa8fa87f6d24d0835a2b4a9341a3eaecb64ac29cd7c5391f35d6175a0fa98ab044e7f2602e1ec3afc886231462ed71b5b80c590b8b41af903ec2c15e5c5931 + languageName: node + linkType: hard + "case-sensitive-paths-webpack-plugin@npm:^2.4.0": version: 2.4.0 resolution: "case-sensitive-paths-webpack-plugin@npm:2.4.0" @@ -12489,10 +12560,10 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^5.2.0": - version: 5.3.0 - resolution: "chalk@npm:5.3.0" - checksum: 10/6373caaab21bd64c405bfc4bd9672b145647fc9482657b5ea1d549b3b2765054e9d3d928870cdf764fb4aad67555f5061538ff247b8310f110c5c888d92397ea +"chalk@npm:^5.2.0, chalk@npm:^5.3.0": + version: 5.4.1 + resolution: "chalk@npm:5.4.1" + checksum: 10/29df3ffcdf25656fed6e95962e2ef86d14dfe03cd50e7074b06bad9ffbbf6089adbb40f75c00744d843685c8d008adaf3aed31476780312553caf07fa86e5bc7 languageName: node linkType: hard @@ -12503,6 +12574,26 @@ __metadata: languageName: node linkType: hard +"change-case@npm:^4.1.2": + version: 4.1.2 + resolution: "change-case@npm:4.1.2" + dependencies: + camel-case: "npm:^4.1.2" + capital-case: "npm:^1.0.4" + constant-case: "npm:^3.0.4" + dot-case: "npm:^3.0.4" + header-case: "npm:^2.0.4" + no-case: "npm:^3.0.4" + param-case: "npm:^3.0.4" + pascal-case: "npm:^3.1.2" + path-case: "npm:^3.0.4" + sentence-case: "npm:^3.0.4" + snake-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10/e4bc4a093a1f7cce8b33896665cf9e456e3bc3cc0def2ad7691b1994cfca99b3188d0a513b16855b01a6bd20692fcde12a7d4d87a5615c4c515bbbf0e651f116 + languageName: node + linkType: hard + "char-regex@npm:^1.0.2": version: 1.0.2 resolution: "char-regex@npm:1.0.2" @@ -12706,6 +12797,15 @@ __metadata: languageName: node linkType: hard +"clean-stack@npm:^4.0.0": + version: 4.2.0 + resolution: "clean-stack@npm:4.2.0" + dependencies: + escape-string-regexp: "npm:5.0.0" + checksum: 10/373f656a31face5c615c0839213b9b542a0a48057abfb1df66900eab4dc2a5c6097628e4a0b5aa559cdfc4e66f8a14ea47be9681773165a44470ef5fb8ccc172 + languageName: node + linkType: hard + "cli-cursor@npm:3.1.0, cli-cursor@npm:^3.1.0": version: 3.1.0 resolution: "cli-cursor@npm:3.1.0" @@ -12715,6 +12815,15 @@ __metadata: languageName: node linkType: hard +"cli-cursor@npm:^5.0.0": + version: 5.0.0 + resolution: "cli-cursor@npm:5.0.0" + dependencies: + restore-cursor: "npm:^5.0.0" + checksum: 10/1eb9a3f878b31addfe8d82c6d915ec2330cec8447ab1f117f4aa34f0137fbb3137ec3466e1c9a65bcb7557f6e486d343f2da57f253a2f668d691372dfa15c090 + languageName: node + linkType: hard + "cli-spinners@npm:2.6.1": version: 2.6.1 resolution: "cli-spinners@npm:2.6.1" @@ -12722,7 +12831,7 @@ __metadata: languageName: node linkType: hard -"cli-spinners@npm:^2.5.0": +"cli-spinners@npm:^2.5.0, cli-spinners@npm:^2.9.2": version: 2.9.2 resolution: "cli-spinners@npm:2.9.2" checksum: 10/a0a863f442df35ed7294424f5491fa1756bd8d2e4ff0c8736531d886cec0ece4d85e8663b77a5afaf1d296e3cbbebff92e2e99f52bbea89b667cbe789b994794 @@ -13174,6 +13283,17 @@ __metadata: languageName: node linkType: hard +"constant-case@npm:^3.0.4": + version: 3.0.4 + resolution: "constant-case@npm:3.0.4" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + upper-case: "npm:^2.0.2" + checksum: 10/6c3346d51afc28d9fae922e966c68eb77a19d94858dba230dd92d7b918b37d36db50f0311e9ecf6847e43e934b1c01406a0936973376ab17ec2c471fbcfb2cf3 + languageName: node + linkType: hard + "constants-browserify@npm:^1.0.0": version: 1.0.0 resolution: "constants-browserify@npm:1.0.0" @@ -14637,6 +14757,22 @@ __metadata: languageName: node linkType: hard +"del@npm:^7.1.0": + version: 7.1.0 + resolution: "del@npm:7.1.0" + dependencies: + globby: "npm:^13.1.2" + graceful-fs: "npm:^4.2.10" + is-glob: "npm:^4.0.3" + is-path-cwd: "npm:^3.0.0" + is-path-inside: "npm:^4.0.0" + p-map: "npm:^5.5.0" + rimraf: "npm:^3.0.2" + slash: "npm:^4.0.0" + checksum: 10/93527e78e95125809ff20a112814b00648ed64af204be1a565862698060c9ec8f5c5fe1a4866725acfde9b0da6423f4b7a7642c1d38cd4b05cbeb643a7b089e3 + languageName: node + linkType: hard + "delaunator@npm:5": version: 5.0.0 resolution: "delaunator@npm:5.0.0" @@ -14695,6 +14831,13 @@ __metadata: languageName: node linkType: hard +"detect-file@npm:^1.0.0": + version: 1.0.0 + resolution: "detect-file@npm:1.0.0" + checksum: 10/1861e4146128622e847abe0e1ed80fef01e78532665858a792267adf89032b7a9c698436137707fcc6f02956c2a6a0052d6a0cef5be3d4b76b1ff0da88e2158a + languageName: node + linkType: hard + "detect-indent@npm:^5.0.0": version: 5.0.0 resolution: "detect-indent@npm:5.0.0" @@ -15098,6 +15241,13 @@ __metadata: languageName: node linkType: hard +"emoji-regex@npm:^10.3.0": + version: 10.4.0 + resolution: "emoji-regex@npm:10.4.0" + checksum: 10/76bb92c5bcf0b6980d37e535156231e4a9d0aa6ab3b9f5eabf7690231d5aa5d5b8e516f36e6804cbdd0f1c23dfef2a60c40ab7bb8aedd890584281a565b97c50 + languageName: node + linkType: hard + "emoji-regex@npm:^8.0.0": version: 8.0.0 resolution: "emoji-regex@npm:8.0.0" @@ -15636,6 +15786,13 @@ __metadata: languageName: node linkType: hard +"escape-string-regexp@npm:5.0.0": + version: 5.0.0 + resolution: "escape-string-regexp@npm:5.0.0" + checksum: 10/20daabe197f3cb198ec28546deebcf24b3dbb1a5a269184381b3116d12f0532e06007f4bc8da25669d6a7f8efb68db0758df4cd981f57bc5b57f521a3e12c59e + languageName: node + linkType: hard + "escape-string-regexp@npm:^1.0.5": version: 1.0.5 resolution: "escape-string-regexp@npm:1.0.5" @@ -16213,6 +16370,15 @@ __metadata: languageName: node linkType: hard +"expand-tilde@npm:^2.0.0, expand-tilde@npm:^2.0.2": + version: 2.0.2 + resolution: "expand-tilde@npm:2.0.2" + dependencies: + homedir-polyfill: "npm:^1.0.1" + checksum: 10/2efe6ed407d229981b1b6ceb552438fbc9e5c7d6a6751ad6ced3e0aa5cf12f0b299da695e90d6c2ac79191b5c53c613e508f7149e4573abfbb540698ddb7301a + languageName: node + linkType: hard + "expect@npm:^29.0.0, expect@npm:^29.7.0": version: 29.7.0 resolution: "expect@npm:29.7.0" @@ -16281,14 +16447,14 @@ __metadata: languageName: node linkType: hard -"extend@npm:~3.0.2": +"extend@npm:^3.0.2, extend@npm:~3.0.2": version: 3.0.2 resolution: "extend@npm:3.0.2" checksum: 10/59e89e2dc798ec0f54b36d82f32a27d5f6472c53974f61ca098db5d4648430b725387b53449a34df38fd0392045434426b012f302b3cc049a6500ccf82877e4e languageName: node linkType: hard -"external-editor@npm:^3.0.3": +"external-editor@npm:^3.0.3, external-editor@npm:^3.1.0": version: 3.1.0 resolution: "external-editor@npm:3.1.0" dependencies: @@ -16358,7 +16524,7 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.0.3, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.2, fast-glob@npm:^3.3.3": +"fast-glob@npm:^3.0.3, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.0, fast-glob@npm:^3.3.2, fast-glob@npm:^3.3.3": version: 3.3.3 resolution: "fast-glob@npm:3.3.3" dependencies: @@ -16671,6 +16837,31 @@ __metadata: languageName: node linkType: hard +"findup-sync@npm:^5.0.0": + version: 5.0.0 + resolution: "findup-sync@npm:5.0.0" + dependencies: + detect-file: "npm:^1.0.0" + is-glob: "npm:^4.0.3" + micromatch: "npm:^4.0.4" + resolve-dir: "npm:^1.0.1" + checksum: 10/576716c77a0e8330b17ae9cba27d1fda8907c8cda7bf33a47f1999e16e089bfc6df4dd62933e0760f430736183c054348c34aa45dd882d49c8c098f55b89ee1d + languageName: node + linkType: hard + +"fined@npm:^2.0.0": + version: 2.0.0 + resolution: "fined@npm:2.0.0" + dependencies: + expand-tilde: "npm:^2.0.2" + is-plain-object: "npm:^5.0.0" + object.defaults: "npm:^1.1.0" + object.pick: "npm:^1.3.0" + parse-filepath: "npm:^1.0.2" + checksum: 10/3c5125a5b4eabb9a9569a9bc55a629d4f463ea8926cca9ee0b54d0e0351715aaed7f245a5372defbb59a0aaccdfefae9dc1a9ac0c7b1167ba8537284db956852 + languageName: node + linkType: hard + "fishery@npm:^2.2.2": version: 2.2.3 resolution: "fishery@npm:2.2.3" @@ -16680,6 +16871,13 @@ __metadata: languageName: node linkType: hard +"flagged-respawn@npm:^2.0.0": + version: 2.0.0 + resolution: "flagged-respawn@npm:2.0.0" + checksum: 10/1b48b1aca4614833bc1c1aa5a7af09232bc284168334b85492e580b51f9bc5ee1a59e4d830934ca91f6dc483300043ac8fe17f88f97f289153f6fcbd8dc9171b + languageName: node + linkType: hard + "flat-cache@npm:^3.0.4": version: 3.1.1 resolution: "flat-cache@npm:3.1.1" @@ -16747,6 +16945,22 @@ __metadata: languageName: node linkType: hard +"for-in@npm:^1.0.1": + version: 1.0.2 + resolution: "for-in@npm:1.0.2" + checksum: 10/09f4ae93ce785d253ac963d94c7f3432d89398bf25ac7a24ed034ca393bf74380bdeccc40e0f2d721a895e54211b07c8fad7132e8157827f6f7f059b70b4043d + languageName: node + linkType: hard + +"for-own@npm:^1.0.0": + version: 1.0.0 + resolution: "for-own@npm:1.0.0" + dependencies: + for-in: "npm:^1.0.1" + checksum: 10/233238f6e9060f61295a7f7c7e3e9de11aaef57e82a108e7f350dc92ae84fe2189848077ac4b8db47fd8edd45337ed8d9f66bd0b1efa4a6a1b3f38aa21b7ab2e + languageName: node + linkType: hard + "foreground-child@npm:^3.1.0": version: 3.1.1 resolution: "foreground-child@npm:3.1.1" @@ -17112,6 +17326,13 @@ __metadata: languageName: node linkType: hard +"get-east-asian-width@npm:^1.0.0": + version: 1.3.0 + resolution: "get-east-asian-width@npm:1.3.0" + checksum: 10/8e8e779eb28701db7fdb1c8cab879e39e6ae23f52dadd89c8aed05869671cee611a65d4f8557b83e981428623247d8bc5d0c7a4ef3ea7a41d826e73600112ad8 + languageName: node + linkType: hard + "get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7": version: 1.2.7 resolution: "get-intrinsic@npm:1.2.7" @@ -17428,6 +17649,17 @@ __metadata: languageName: node linkType: hard +"global-modules@npm:^1.0.0": + version: 1.0.0 + resolution: "global-modules@npm:1.0.0" + dependencies: + global-prefix: "npm:^1.0.1" + is-windows: "npm:^1.0.1" + resolve-dir: "npm:^1.0.0" + checksum: 10/e4031a01c0c7401349bb69e1499c7268d636552b16374c0002d677c7a6185da6782a2927a7a3a7c046eb7be97cd26b3c7b1b736f9818ecc7ac09e9d61449065e + languageName: node + linkType: hard + "global-modules@npm:^2.0.0": version: 2.0.0 resolution: "global-modules@npm:2.0.0" @@ -17437,6 +17669,19 @@ __metadata: languageName: node linkType: hard +"global-prefix@npm:^1.0.1": + version: 1.0.2 + resolution: "global-prefix@npm:1.0.2" + dependencies: + expand-tilde: "npm:^2.0.2" + homedir-polyfill: "npm:^1.0.1" + ini: "npm:^1.3.4" + is-windows: "npm:^1.0.1" + which: "npm:^1.2.14" + checksum: 10/68cf78f81cd85310095ca1f0ec22dd5f43a1059646b2c7b3fc4a7c9ce744356e66ca833adda4e5753e38021847aaec393a159a029ba2d257c08ccb3f00ca2899 + languageName: node + linkType: hard + "global-prefix@npm:^3.0.0": version: 3.0.0 resolution: "global-prefix@npm:3.0.0" @@ -17509,6 +17754,19 @@ __metadata: languageName: node linkType: hard +"globby@npm:^13.1.2, globby@npm:^13.2.2": + version: 13.2.2 + resolution: "globby@npm:13.2.2" + dependencies: + dir-glob: "npm:^3.0.1" + fast-glob: "npm:^3.3.0" + ignore: "npm:^5.2.4" + merge2: "npm:^1.4.1" + slash: "npm:^4.0.0" + checksum: 10/4494a9d2162a7e4d327988b26be66d8eab87d7f59a83219e74b065e2c3ced23698f68fb10482bf9337133819281803fb886d6ae06afbb2affa743623eb0b1949 + languageName: node + linkType: hard + "globby@npm:^14.0.0": version: 14.0.1 resolution: "globby@npm:14.0.1" @@ -17800,6 +18058,7 @@ __metadata: ol-ext: "npm:4.0.26" openapi-types: "npm:^12.1.3" pdf-parse: "npm:^1.1.1" + plop: "npm:^4.0.1" pluralize: "npm:^8.0.0" postcss: "npm:8.5.1" postcss-loader: "npm:8.1.1" @@ -17947,12 +18206,12 @@ __metadata: languageName: node linkType: hard -"handlebars@npm:^4.7.7": - version: 4.7.7 - resolution: "handlebars@npm:4.7.7" +"handlebars@npm:^4.7.7, handlebars@npm:^4.7.8": + version: 4.7.8 + resolution: "handlebars@npm:4.7.8" dependencies: minimist: "npm:^1.2.5" - neo-async: "npm:^2.6.0" + neo-async: "npm:^2.6.2" source-map: "npm:^0.6.1" uglify-js: "npm:^3.1.4" wordwrap: "npm:^1.0.0" @@ -17961,7 +18220,7 @@ __metadata: optional: true bin: handlebars: bin/handlebars - checksum: 10/617b1e689b7577734abc74564bdb8cdaddf8fd48ce72afdb489f426e9c60a7d6ee2a2707c023720c4059070128243c948bded8f2716e4543378033e3971b85ea + checksum: 10/bd528f4dd150adf67f3f857118ef0fa43ff79a153b1d943fa0a770f2599e38b25a7a0dbac1a3611a4ec86970fd2325a81310fb788b5c892308c9f8743bd02e11 languageName: node linkType: hard @@ -18072,6 +18331,16 @@ __metadata: languageName: node linkType: hard +"header-case@npm:^2.0.4": + version: 2.0.4 + resolution: "header-case@npm:2.0.4" + dependencies: + capital-case: "npm:^1.0.4" + tslib: "npm:^2.0.3" + checksum: 10/571c83eeb25e8130d172218712f807c0b96d62b020981400bccc1503a7cf14b09b8b10498a962d2739eccf231d950e3848ba7d420b58a6acd2f9283439546cd9 + languageName: node + linkType: hard + "headers-polyfill@npm:^4.0.2": version: 4.0.2 resolution: "headers-polyfill@npm:4.0.2" @@ -18165,6 +18434,15 @@ __metadata: languageName: node linkType: hard +"homedir-polyfill@npm:^1.0.1": + version: 1.0.3 + resolution: "homedir-polyfill@npm:1.0.3" + dependencies: + parse-passwd: "npm:^1.0.0" + checksum: 10/18dd4db87052c6a2179d1813adea0c4bfcfa4f9996f0e226fefb29eb3d548e564350fa28ec46b0bf1fbc0a1d2d6922ceceb80093115ea45ff8842a4990139250 + languageName: node + linkType: hard + "hookified@npm:^1.6.0": version: 1.7.0 resolution: "hookified@npm:1.7.0" @@ -18802,6 +19080,13 @@ __metadata: languageName: node linkType: hard +"indent-string@npm:^5.0.0": + version: 5.0.0 + resolution: "indent-string@npm:5.0.0" + checksum: 10/e466c27b6373440e6d84fbc19e750219ce25865cb82d578e41a6053d727e5520dc5725217d6eb1cc76005a1bb1696a0f106d84ce7ebda3033b963a38583fb3b3 + languageName: node + linkType: hard + "infer-owner@npm:^1.0.4": version: 1.0.4 resolution: "infer-owner@npm:1.0.4" @@ -18840,7 +19125,7 @@ __metadata: languageName: node linkType: hard -"ini@npm:^1.3.2, ini@npm:^1.3.5, ini@npm:^1.3.8": +"ini@npm:^1.3.2, ini@npm:^1.3.4, ini@npm:^1.3.5, ini@npm:^1.3.8": version: 1.3.8 resolution: "ini@npm:1.3.8" checksum: 10/314ae176e8d4deb3def56106da8002b462221c174ddb7ce0c49ee72c8cd1f9044f7b10cc555a7d8850982c3b9ca96fc212122749f5234bc2b6fb05fb942ed566 @@ -18901,6 +19186,26 @@ __metadata: languageName: node linkType: hard +"inquirer@npm:^9.2.10": + version: 9.3.7 + resolution: "inquirer@npm:9.3.7" + dependencies: + "@inquirer/figures": "npm:^1.0.3" + ansi-escapes: "npm:^4.3.2" + cli-width: "npm:^4.1.0" + external-editor: "npm:^3.1.0" + mute-stream: "npm:1.0.0" + ora: "npm:^5.4.1" + run-async: "npm:^3.0.0" + rxjs: "npm:^7.8.1" + string-width: "npm:^4.2.3" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^6.2.0" + yoctocolors-cjs: "npm:^2.1.2" + checksum: 10/92d0a0f55701e05a8dd3624eab0c03c6e96de18285cefdbef4edc5c1a77c8283361700e088db275ec5d5b36c45ff9428127125d78428e07ea4c5574f55b17176 + languageName: node + linkType: hard + "internal-slot@npm:^1.1.0": version: 1.1.0 resolution: "internal-slot@npm:1.1.0" @@ -18978,6 +19283,16 @@ __metadata: languageName: node linkType: hard +"is-absolute@npm:^1.0.0": + version: 1.0.0 + resolution: "is-absolute@npm:1.0.0" + dependencies: + is-relative: "npm:^1.0.0" + is-windows: "npm:^1.0.1" + checksum: 10/9d16b2605eda3f3ce755410f1d423e327ad3a898bcb86c9354cf63970ed3f91ba85e9828aa56f5d6a952b9fae43d0477770f78d37409ae8ecc31e59ebc279b27 + languageName: node + linkType: hard + "is-alphabetical@npm:^1.0.0": version: 1.0.4 resolution: "is-alphabetical@npm:1.0.4" @@ -19278,6 +19593,13 @@ __metadata: languageName: node linkType: hard +"is-interactive@npm:^2.0.0": + version: 2.0.0 + resolution: "is-interactive@npm:2.0.0" + checksum: 10/e8d52ad490bed7ae665032c7675ec07732bbfe25808b0efbc4d5a76b1a1f01c165f332775c63e25e9a03d319ebb6b24f571a9e902669fc1e40b0a60b5be6e26c + languageName: node + linkType: hard + "is-lambda@npm:^1.0.1": version: 1.0.1 resolution: "is-lambda@npm:1.0.1" @@ -19354,6 +19676,13 @@ __metadata: languageName: node linkType: hard +"is-path-cwd@npm:^3.0.0": + version: 3.0.0 + resolution: "is-path-cwd@npm:3.0.0" + checksum: 10/bc34d13b6a03dfca4a3ab6a8a5ba78ae4b24f4f1db4b2b031d2760c60d0913bd16a4b980dcb4e590adfc906649d5f5132684079a3972bd219da49deebb9adea8 + languageName: node + linkType: hard + "is-path-inside@npm:^3.0.2": version: 3.0.3 resolution: "is-path-inside@npm:3.0.3" @@ -19361,6 +19690,13 @@ __metadata: languageName: node linkType: hard +"is-path-inside@npm:^4.0.0": + version: 4.0.0 + resolution: "is-path-inside@npm:4.0.0" + checksum: 10/8810fa11c58e6360b82c3e0d6cd7d9c7d0392d3ac9eb10f980b81f9839f40ac6d1d6d6f05d069db0d227759801228f0b072e1b6c343e4469b065ab5fe0b68fe5 + languageName: node + linkType: hard + "is-plain-obj@npm:^1.0.0, is-plain-obj@npm:^1.1.0": version: 1.1.0 resolution: "is-plain-obj@npm:1.1.0" @@ -19424,6 +19760,15 @@ __metadata: languageName: node linkType: hard +"is-relative@npm:^1.0.0": + version: 1.0.0 + resolution: "is-relative@npm:1.0.0" + dependencies: + is-unc-path: "npm:^1.0.0" + checksum: 10/3271a0df109302ef5e14a29dcd5d23d9788e15ade91a40b942b035827ffbb59f7ce9ff82d036ea798541a52913cbf9d2d0b66456340887b51f3542d57b5a4c05 + languageName: node + linkType: hard + "is-set@npm:^2.0.3": version: 2.0.3 resolution: "is-set@npm:2.0.3" @@ -19509,6 +19854,15 @@ __metadata: languageName: node linkType: hard +"is-unc-path@npm:^1.0.0": + version: 1.0.0 + resolution: "is-unc-path@npm:1.0.0" + dependencies: + unc-path-regex: "npm:^0.1.2" + checksum: 10/e8abfde203f7409f5b03a5f1f8636e3a41e78b983702ef49d9343eb608cdfe691429398e8815157519b987b739bcfbc73ae7cf4c8582b0ab66add5171088eab6 + languageName: node + linkType: hard + "is-unicode-supported@npm:^0.1.0": version: 0.1.0 resolution: "is-unicode-supported@npm:0.1.0" @@ -19516,6 +19870,20 @@ __metadata: languageName: node linkType: hard +"is-unicode-supported@npm:^1.3.0": + version: 1.3.0 + resolution: "is-unicode-supported@npm:1.3.0" + checksum: 10/20a1fc161afafaf49243551a5ac33b6c4cf0bbcce369fcd8f2951fbdd000c30698ce320de3ee6830497310a8f41880f8066d440aa3eb0a853e2aa4836dd89abc + languageName: node + linkType: hard + +"is-unicode-supported@npm:^2.0.0": + version: 2.1.0 + resolution: "is-unicode-supported@npm:2.1.0" + checksum: 10/f254e3da6b0ab1a57a94f7273a7798dd35d1d45b227759f600d0fa9d5649f9c07fa8d3c8a6360b0e376adf916d151ec24fc9a50c5295c58bae7ca54a76a063f9 + languageName: node + linkType: hard + "is-valid-glob@npm:^1.0.0": version: 1.0.0 resolution: "is-valid-glob@npm:1.0.0" @@ -19556,6 +19924,13 @@ __metadata: languageName: node linkType: hard +"is-windows@npm:^1.0.1": + version: 1.0.2 + resolution: "is-windows@npm:1.0.2" + checksum: 10/438b7e52656fe3b9b293b180defb4e448088e7023a523ec21a91a80b9ff8cdb3377ddb5b6e60f7c7de4fa8b63ab56e121b6705fe081b3cf1b828b0a380009ad7 + languageName: node + linkType: hard + "is-wsl@npm:^2.2.0": version: 2.2.0 resolution: "is-wsl@npm:2.2.0" @@ -19595,6 +19970,13 @@ __metadata: languageName: node linkType: hard +"isbinaryfile@npm:^5.0.0": + version: 5.0.4 + resolution: "isbinaryfile@npm:5.0.4" + checksum: 10/6162e900b17e6c73da6138667d6b195ed234f9fd9d073e7c8c07ee36657e63b6a69d73da55f522d45a1928f5da4642b5d25d27e24ebd3bb68b83647d594bee79 + languageName: node + linkType: hard + "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -19609,7 +19991,7 @@ __metadata: languageName: node linkType: hard -"isobject@npm:^3.0.1": +"isobject@npm:^3.0.0, isobject@npm:^3.0.1": version: 3.0.1 resolution: "isobject@npm:3.0.1" checksum: 10/db85c4c970ce30693676487cca0e61da2ca34e8d4967c2e1309143ff910c207133a969f9e4ddb2dc6aba670aabce4e0e307146c310350b298e74a31f7d464703 @@ -21048,6 +21430,22 @@ __metadata: languageName: node linkType: hard +"liftoff@npm:^4.0.0": + version: 4.0.0 + resolution: "liftoff@npm:4.0.0" + dependencies: + extend: "npm:^3.0.2" + findup-sync: "npm:^5.0.0" + fined: "npm:^2.0.0" + flagged-respawn: "npm:^2.0.0" + is-plain-object: "npm:^5.0.0" + object.map: "npm:^1.0.1" + rechoir: "npm:^0.8.0" + resolve: "npm:^1.20.0" + checksum: 10/bb9f467ed4d792ce6b519558a2d7b9ef4ec28c7aa09270981395631d456c031128881c2bd31375c7c9d416abb4fc1263dbf17d9ba46bac9df0e88e04c378c699 + languageName: node + linkType: hard + "lilconfig@npm:^3.1.2, lilconfig@npm:^3.1.3": version: 3.1.3 resolution: "lilconfig@npm:3.1.3" @@ -21344,6 +21742,16 @@ __metadata: languageName: node linkType: hard +"log-symbols@npm:^6.0.0": + version: 6.0.0 + resolution: "log-symbols@npm:6.0.0" + dependencies: + chalk: "npm:^5.3.0" + is-unicode-supported: "npm:^1.3.0" + checksum: 10/510cdda36700cbcd87a2a691ea08d310a6c6b449084018f7f2ec4f732ca5e51b301ff1327aadd96f53c08318e616276c65f7fe22f2a16704fb0715d788bc3c33 + languageName: node + linkType: hard + "log-update@npm:^4.0.0": version: 4.0.0 resolution: "log-update@npm:4.0.0" @@ -21584,6 +21992,15 @@ __metadata: languageName: node linkType: hard +"make-iterator@npm:^1.0.0": + version: 1.0.1 + resolution: "make-iterator@npm:1.0.1" + dependencies: + kind-of: "npm:^6.0.2" + checksum: 10/d38afc388f4374b15c0622d4fa4d3e8c3154e3a6ba35b01e9a5179c127d7dd09a91fa571056aa9e041981b39f80bdbab035c05475e56ef675a18bdf550f0cb6a + languageName: node + linkType: hard + "makeerror@npm:1.0.12": version: 1.0.12 resolution: "makeerror@npm:1.0.12" @@ -21593,6 +22010,13 @@ __metadata: languageName: node linkType: hard +"map-cache@npm:^0.2.0": + version: 0.2.2 + resolution: "map-cache@npm:0.2.2" + checksum: 10/3067cea54285c43848bb4539f978a15dedc63c03022abeec6ef05c8cb6829f920f13b94bcaf04142fc6a088318e564c4785704072910d120d55dbc2e0c421969 + languageName: node + linkType: hard + "map-obj@npm:^1.0.0": version: 1.0.1 resolution: "map-obj@npm:1.0.1" @@ -21858,6 +22282,13 @@ __metadata: languageName: node linkType: hard +"mimic-function@npm:^5.0.0": + version: 5.0.1 + resolution: "mimic-function@npm:5.0.1" + checksum: 10/eb5893c99e902ccebbc267c6c6b83092966af84682957f79313311edb95e8bb5f39fb048d77132b700474d1c86d90ccc211e99bae0935447a4834eb4c882982c + languageName: node + linkType: hard + "min-indent@npm:^1.0.0, min-indent@npm:^1.0.1": version: 1.0.1 resolution: "min-indent@npm:1.0.1" @@ -22127,6 +22558,15 @@ __metadata: languageName: node linkType: hard +"mkdirp@npm:^3.0.1": + version: 3.0.1 + resolution: "mkdirp@npm:3.0.1" + bin: + mkdirp: dist/cjs/src/bin.js + checksum: 10/16fd79c28645759505914561e249b9a1f5fe3362279ad95487a4501e4467abeb714fd35b95307326b8fd03f3c7719065ef11a6f97b7285d7888306d1bd2232ba + languageName: node + linkType: hard + "mktemp@npm:~0.4.0": version: 0.4.0 resolution: "mktemp@npm:0.4.0" @@ -22372,7 +22812,7 @@ __metadata: languageName: node linkType: hard -"mute-stream@npm:^1.0.0": +"mute-stream@npm:1.0.0, mute-stream@npm:^1.0.0": version: 1.0.0 resolution: "mute-stream@npm:1.0.0" checksum: 10/36fc968b0e9c9c63029d4f9dc63911950a3bdf55c9a87f58d3a266289b67180201cade911e7699f8b2fa596b34c9db43dad37649e3f7fdd13c3bb9edb0017ee7 @@ -22437,7 +22877,7 @@ __metadata: languageName: node linkType: hard -"neo-async@npm:^2.6.0, neo-async@npm:^2.6.2": +"neo-async@npm:^2.6.2": version: 2.6.2 resolution: "neo-async@npm:2.6.2" checksum: 10/1a7948fea86f2b33ec766bc899c88796a51ba76a4afc9026764aedc6e7cde692a09067031e4a1bf6db4f978ccd99e7f5b6c03fe47ad9865c3d4f99050d67e002 @@ -22633,6 +23073,27 @@ __metadata: languageName: node linkType: hard +"node-plop@npm:^0.32.0": + version: 0.32.0 + resolution: "node-plop@npm:0.32.0" + dependencies: + "@types/inquirer": "npm:^9.0.3" + change-case: "npm:^4.1.2" + del: "npm:^7.1.0" + globby: "npm:^13.2.2" + handlebars: "npm:^4.7.8" + inquirer: "npm:^9.2.10" + isbinaryfile: "npm:^5.0.0" + lodash.get: "npm:^4.4.2" + lower-case: "npm:^2.0.2" + mkdirp: "npm:^3.0.1" + resolve: "npm:^1.22.4" + title-case: "npm:^3.0.3" + upper-case: "npm:^2.0.2" + checksum: 10/e584ecd9090e4ce310af93357e0dcbbb073e528374e020cf796c24e85ee5d2141308c5bb4dca533141f24e94bf5a4e4c6d86621d1322917915ea6f66cc6b3326 + languageName: node + linkType: hard + "node-readfiles@npm:^0.2.0": version: 0.2.0 resolution: "node-readfiles@npm:0.2.0" @@ -23066,6 +23527,18 @@ __metadata: languageName: node linkType: hard +"object.defaults@npm:^1.1.0": + version: 1.1.0 + resolution: "object.defaults@npm:1.1.0" + dependencies: + array-each: "npm:^1.0.1" + array-slice: "npm:^1.0.0" + for-own: "npm:^1.0.0" + isobject: "npm:^3.0.0" + checksum: 10/9b194806eb9b5cf8c956d20e9869b3c7431c85748d761a570b45beb71041119408ca2c3d380fe43d4340019e6d03fab91d60842cb3d7259ceffd9e582cd79fb8 + languageName: node + linkType: hard + "object.entries@npm:^1.1.8": version: 1.1.8 resolution: "object.entries@npm:1.1.8" @@ -23100,6 +23573,16 @@ __metadata: languageName: node linkType: hard +"object.map@npm:^1.0.1": + version: 1.0.1 + resolution: "object.map@npm:1.0.1" + dependencies: + for-own: "npm:^1.0.0" + make-iterator: "npm:^1.0.0" + checksum: 10/c2b945a309f789441fae30e4c0772066b45ad03eb1c0f91b8ae117700c975676652b356f61635fe0b21ae021d98f10a04d2f1c6cf30aef14111154e756b162d7 + languageName: node + linkType: hard + "object.omit@npm:^3.0.0": version: 3.0.0 resolution: "object.omit@npm:3.0.0" @@ -23109,6 +23592,15 @@ __metadata: languageName: node linkType: hard +"object.pick@npm:^1.3.0": + version: 1.3.0 + resolution: "object.pick@npm:1.3.0" + dependencies: + isobject: "npm:^3.0.1" + checksum: 10/92d7226a6b581d0d62694a5632b6a1594c81b3b5a4eb702a7662e0b012db532557067d6f773596c577f75322eba09cdca37ca01ea79b6b29e3e17365f15c615e + languageName: node + linkType: hard + "object.values@npm:^1.1.6, object.values@npm:^1.2.0, object.values@npm:^1.2.1": version: 1.2.1 resolution: "object.values@npm:1.2.1" @@ -23218,6 +23710,15 @@ __metadata: languageName: node linkType: hard +"onetime@npm:^7.0.0": + version: 7.0.0 + resolution: "onetime@npm:7.0.0" + dependencies: + mimic-function: "npm:^5.0.0" + checksum: 10/eb08d2da9339819e2f9d52cab9caf2557d80e9af8c7d1ae86e1a0fef027d00a88e9f5bd67494d350df360f7c559fbb44e800b32f310fb989c860214eacbb561c + languageName: node + linkType: hard + "open@npm:^10.0.3": version: 10.0.3 resolution: "open@npm:10.0.3" @@ -23322,6 +23823,23 @@ __metadata: languageName: node linkType: hard +"ora@npm:^8.0.0": + version: 8.2.0 + resolution: "ora@npm:8.2.0" + dependencies: + chalk: "npm:^5.3.0" + cli-cursor: "npm:^5.0.0" + cli-spinners: "npm:^2.9.2" + is-interactive: "npm:^2.0.0" + is-unicode-supported: "npm:^2.0.0" + log-symbols: "npm:^6.0.0" + stdin-discarder: "npm:^0.2.2" + string-width: "npm:^7.2.0" + strip-ansi: "npm:^7.1.0" + checksum: 10/cea932fdcb29549cd7b5af81f427760986429cadc752b1dd4bf31bc6821f5ba137e1ef9a18cde7bdfbe5b4e3d3201e76b048765c51a27b15d18c57ac0e0a909a + languageName: node + linkType: hard + "os-tmpdir@npm:~1.0.2": version: 1.0.2 resolution: "os-tmpdir@npm:1.0.2" @@ -23458,6 +23976,15 @@ __metadata: languageName: node linkType: hard +"p-map@npm:^5.5.0": + version: 5.5.0 + resolution: "p-map@npm:5.5.0" + dependencies: + aggregate-error: "npm:^4.0.0" + checksum: 10/089a709d2525208a965b7907cc8e58af950542629b538198fc142c40e7f36b3b492dd6a46a1279515ccab58bb6f047e04593c0ab5ef4539d312adf7f761edf55 + languageName: node + linkType: hard + "p-pipe@npm:3.1.0": version: 3.1.0 resolution: "p-pipe@npm:3.1.0" @@ -23657,6 +24184,17 @@ __metadata: languageName: node linkType: hard +"parse-filepath@npm:^1.0.2": + version: 1.0.2 + resolution: "parse-filepath@npm:1.0.2" + dependencies: + is-absolute: "npm:^1.0.0" + map-cache: "npm:^0.2.0" + path-root: "npm:^0.1.1" + checksum: 10/6794c3f38d3921f0f7cc63fb1fb0c4d04cd463356ad389c8ce6726d3c50793b9005971f4138975a6d7025526058d5e65e9bfe634d0765e84c4e2571152665a69 + languageName: node + linkType: hard + "parse-headers@npm:^2.0.2": version: 2.0.4 resolution: "parse-headers@npm:2.0.4" @@ -23696,6 +24234,13 @@ __metadata: languageName: node linkType: hard +"parse-passwd@npm:^1.0.0": + version: 1.0.0 + resolution: "parse-passwd@npm:1.0.0" + checksum: 10/4e55e0231d58f828a41d0f1da2bf2ff7bcef8f4cb6146e69d16ce499190de58b06199e6bd9b17fbf0d4d8aef9052099cdf8c4f13a6294b1a522e8e958073066e + languageName: node + linkType: hard + "parse-path@npm:^7.0.0": version: 7.0.0 resolution: "parse-path@npm:7.0.0" @@ -23776,6 +24321,16 @@ __metadata: languageName: node linkType: hard +"path-case@npm:^3.0.4": + version: 3.0.4 + resolution: "path-case@npm:3.0.4" + dependencies: + dot-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10/61de0526222629f65038a66f63330dd22d5b54014ded6636283e1d15364da38b3cf29e4433aa3f9d8b0dba407ae2b059c23b0104a34ee789944b1bc1c5c7e06d + languageName: node + linkType: hard + "path-exists@npm:^3.0.0": version: 3.0.0 resolution: "path-exists@npm:3.0.0" @@ -23825,6 +24380,22 @@ __metadata: languageName: node linkType: hard +"path-root-regex@npm:^0.1.0": + version: 0.1.2 + resolution: "path-root-regex@npm:0.1.2" + checksum: 10/dcd75d1f8e93faabe35a58e875b0f636839b3658ff2ad8c289463c40bc1a844debe0dab73c3398ef9dc8f6ec6c319720aff390cf4633763ddcf3cf4b1bbf7e8b + languageName: node + linkType: hard + +"path-root@npm:^0.1.1": + version: 0.1.1 + resolution: "path-root@npm:0.1.1" + dependencies: + path-root-regex: "npm:^0.1.0" + checksum: 10/ff88aebfc1c59ace510cc06703d67692a11530989920427625e52b66a303ca9b3d4059b0b7d0b2a73248d1ad29bcb342b8b786ec00592f3101d38a45fd3b2e08 + languageName: node + linkType: hard + "path-scurry@npm:^1.11.1, path-scurry@npm:^1.6.1": version: 1.11.1 resolution: "path-scurry@npm:1.11.1" @@ -24064,6 +24635,24 @@ __metadata: languageName: node linkType: hard +"plop@npm:^4.0.1": + version: 4.0.1 + resolution: "plop@npm:4.0.1" + dependencies: + "@types/liftoff": "npm:^4.0.3" + chalk: "npm:^5.3.0" + interpret: "npm:^3.1.1" + liftoff: "npm:^4.0.0" + minimist: "npm:^1.2.8" + node-plop: "npm:^0.32.0" + ora: "npm:^8.0.0" + v8flags: "npm:^4.0.1" + bin: + plop: bin/plop.js + checksum: 10/cb7f4c2b8fe3b1f3735a0a9ab92a7b1b0aa940293ce85840b2ec55821b62a697e1e29e9d68ff298d23eb5501e965fc707f586a962a4bf213dcf606f2589837a4 + languageName: node + linkType: hard + "pluralize@npm:8.0.0, pluralize@npm:^8.0.0": version: 8.0.0 resolution: "pluralize@npm:8.0.0" @@ -26774,6 +27363,16 @@ __metadata: languageName: node linkType: hard +"resolve-dir@npm:^1.0.0, resolve-dir@npm:^1.0.1": + version: 1.0.1 + resolution: "resolve-dir@npm:1.0.1" + dependencies: + expand-tilde: "npm:^2.0.0" + global-modules: "npm:^1.0.0" + checksum: 10/ef736b8ed60d6645c3b573da17d329bfb50ec4e1d6c5ffd6df49e3497acef9226f9810ea6823b8ece1560e01dcb13f77a9f6180d4f242d00cc9a8f4de909c65c + languageName: node + linkType: hard + "resolve-from@npm:5.0.0, resolve-from@npm:^5.0.0": version: 5.0.0 resolution: "resolve-from@npm:5.0.0" @@ -26896,6 +27495,16 @@ __metadata: languageName: node linkType: hard +"restore-cursor@npm:^5.0.0": + version: 5.1.0 + resolution: "restore-cursor@npm:5.1.0" + dependencies: + onetime: "npm:^7.0.0" + signal-exit: "npm:^4.1.0" + checksum: 10/838dd54e458d89cfbc1a923b343c1b0f170a04100b4ce1733e97531842d7b440463967e521216e8ab6c6f8e89df877acc7b7f4c18ec76e99fb9bf5a60d358d2c + languageName: node + linkType: hard + "ret@npm:^0.2.0": version: 0.2.2 resolution: "ret@npm:0.2.2" @@ -27183,6 +27792,13 @@ __metadata: languageName: node linkType: hard +"run-async@npm:^3.0.0": + version: 3.0.0 + resolution: "run-async@npm:3.0.0" + checksum: 10/97fb8747f7765b77ebcd311d3a33548099336f04c6434e0763039b98c1de0f1b4421000695aff8751f309c0b995d8dfd620c1f1e4c35572da38c101488165305 + languageName: node + linkType: hard + "run-parallel@npm:^1.1.9": version: 1.2.0 resolution: "run-parallel@npm:1.2.0" @@ -27199,7 +27815,7 @@ __metadata: languageName: node linkType: hard -"rxjs@npm:7.8.1, rxjs@npm:^7.5.1, rxjs@npm:^7.5.5": +"rxjs@npm:7.8.1": version: 7.8.1 resolution: "rxjs@npm:7.8.1" dependencies: @@ -27208,6 +27824,15 @@ __metadata: languageName: node linkType: hard +"rxjs@npm:^7.2.0, rxjs@npm:^7.5.1, rxjs@npm:^7.5.5, rxjs@npm:^7.8.1": + version: 7.8.2 + resolution: "rxjs@npm:7.8.2" + dependencies: + tslib: "npm:^2.1.0" + checksum: 10/03dff09191356b2b87d94fbc1e97c4e9eb3c09d4452399dddd451b09c2f1ba8d56925a40af114282d7bc0c6fe7514a2236ca09f903cf70e4bbf156650dddb49d + languageName: node + linkType: hard + "safe-array-concat@npm:^1.1.3": version: 1.1.3 resolution: "safe-array-concat@npm:1.1.3" @@ -27490,6 +28115,17 @@ __metadata: languageName: node linkType: hard +"sentence-case@npm:^3.0.4": + version: 3.0.4 + resolution: "sentence-case@npm:3.0.4" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + upper-case-first: "npm:^2.0.2" + checksum: 10/3cfe6c0143e649132365695706702d7f729f484fa7b25f43435876efe7af2478243eefb052bacbcce10babf9319fd6b5b6bc59b94c80a1c819bcbb40651465d5 + languageName: node + linkType: hard + "serialize-error@npm:^8.1.0": version: 8.1.0 resolution: "serialize-error@npm:8.1.0" @@ -27850,6 +28486,13 @@ __metadata: languageName: node linkType: hard +"slash@npm:^4.0.0": + version: 4.0.0 + resolution: "slash@npm:4.0.0" + checksum: 10/da8e4af73712253acd21b7853b7e0dbba776b786e82b010a5bfc8b5051a1db38ed8aba8e1e8f400dd2c9f373be91eb1c42b66e91abb407ff42b10feece5e1d2d + languageName: node + linkType: hard + "slash@npm:^5.0.0, slash@npm:^5.1.0": version: 5.1.0 resolution: "slash@npm:5.1.0" @@ -28041,6 +28684,16 @@ __metadata: languageName: node linkType: hard +"snake-case@npm:^3.0.4": + version: 3.0.4 + resolution: "snake-case@npm:3.0.4" + dependencies: + dot-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10/0a7a79900bbb36f8aaa922cf111702a3647ac6165736d5dc96d3ef367efc50465cac70c53cd172c382b022dac72ec91710608e5393de71f76d7142e6fd80e8a3 + languageName: node + linkType: hard + "socket.io-adapter@npm:~2.5.2": version: 2.5.5 resolution: "socket.io-adapter@npm:2.5.5" @@ -28498,6 +29151,13 @@ __metadata: languageName: node linkType: hard +"stdin-discarder@npm:^0.2.2": + version: 0.2.2 + resolution: "stdin-discarder@npm:0.2.2" + checksum: 10/642ffd05bd5b100819d6b24a613d83c6e3857c6de74eb02fc51506fa61dc1b0034665163831873868157c4538d71e31762bcf319be86cea04c3aba5336470478 + languageName: node + linkType: hard + "storybook@npm:^8.6.2": version: 8.6.2 resolution: "storybook@npm:8.6.2" @@ -28619,6 +29279,17 @@ __metadata: languageName: node linkType: hard +"string-width@npm:^7.2.0": + version: 7.2.0 + resolution: "string-width@npm:7.2.0" + dependencies: + emoji-regex: "npm:^10.3.0" + get-east-asian-width: "npm:^1.0.0" + strip-ansi: "npm:^7.1.0" + checksum: 10/42f9e82f61314904a81393f6ef75b832c39f39761797250de68c041d8ba4df2ef80db49ab6cd3a292923a6f0f409b8c9980d120f7d32c820b4a8a84a2598a295 + languageName: node + linkType: hard + "string.prototype.includes@npm:^2.0.1": version: 2.0.1 resolution: "string.prototype.includes@npm:2.0.1" @@ -28742,7 +29413,7 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^7.0.1": +"strip-ansi@npm:^7.0.1, strip-ansi@npm:^7.1.0": version: 7.1.0 resolution: "strip-ansi@npm:7.1.0" dependencies: @@ -29400,6 +30071,15 @@ __metadata: languageName: node linkType: hard +"title-case@npm:^3.0.3": + version: 3.0.3 + resolution: "title-case@npm:3.0.3" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10/369fe90f650a66205c34ebef63a69c6d1fd411ae3aad23db0aae165ddb881af50e67c6ea6800d605bc2b9e0ab5f22dada58fe97a1a7e7f3131ee0ef176cc65ec + languageName: node + linkType: hard + "tlds@npm:1.252.0": version: 1.252.0 resolution: "tlds@npm:1.252.0" @@ -30084,6 +30764,13 @@ __metadata: languageName: node linkType: hard +"unc-path-regex@npm:^0.1.2": + version: 0.1.2 + resolution: "unc-path-regex@npm:0.1.2" + checksum: 10/a05fa2006bf4606051c10fc7968f08ce7b28fa646befafa282813aeb1ac1a56f65cb1b577ca7851af2726198d59475bb49b11776036257b843eaacee2860a4ec + languageName: node + linkType: hard + "underscore.string@npm:~3.3.4": version: 3.3.6 resolution: "underscore.string@npm:3.3.6" @@ -30290,6 +30977,24 @@ __metadata: languageName: node linkType: hard +"upper-case-first@npm:^2.0.2": + version: 2.0.2 + resolution: "upper-case-first@npm:2.0.2" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10/4487db4701effe3b54ced4b3e4aa4d9ab06c548f97244d04aafb642eedf96a76d5a03cf5f38f10f415531d5792d1ac6e1b50f2a76984dc6964ad530f12876409 + languageName: node + linkType: hard + +"upper-case@npm:^2.0.2": + version: 2.0.2 + resolution: "upper-case@npm:2.0.2" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10/508723a2b03ab90cf1d6b7e0397513980fab821cbe79c87341d0e96cedefadf0d85f9d71eac24ab23f526a041d585a575cfca120a9f920e44eb4f8a7cf89121c + languageName: node + linkType: hard + "uri-js@npm:^4.2.2": version: 4.4.1 resolution: "uri-js@npm:4.4.1" @@ -30469,6 +31174,13 @@ __metadata: languageName: node linkType: hard +"v8flags@npm:^4.0.1": + version: 4.0.1 + resolution: "v8flags@npm:4.0.1" + checksum: 10/69863ede75ff79579654951c78724c084bc337d0ebe1d9bffc6924f3f2bd0b40a9eb4c568fc795201d5eb72311b77e5d75a7e1544faa12355412360dc37d76e2 + languageName: node + linkType: hard + "validate-npm-package-license@npm:3.0.4, validate-npm-package-license@npm:^3.0.1, validate-npm-package-license@npm:^3.0.4": version: 3.0.4 resolution: "validate-npm-package-license@npm:3.0.4" @@ -31213,7 +31925,7 @@ __metadata: languageName: node linkType: hard -"which@npm:^1.3.1": +"which@npm:^1.2.14, which@npm:^1.3.1": version: 1.3.1 resolution: "which@npm:1.3.1" dependencies: From ee93dbd2bd58ed8247a75e82e7d4e1306d56f3d4 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Fri, 25 Apr 2025 09:22:02 +0100 Subject: [PATCH 108/146] Chore: Run fronted unit tests + linting/typecheck against enterprise (#104479) * run unit tests + linting/typecheck against enterprise * update permissions * kick CI * keep job names the same --- .github/workflows/frontend-lint.yml | 47 ++++++++++++++++++++ .github/workflows/pr-frontend-unit-tests.yml | 34 ++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/.github/workflows/frontend-lint.yml b/.github/workflows/frontend-lint.yml index cad4294407b..148838465fa 100644 --- a/.github/workflows/frontend-lint.yml +++ b/.github/workflows/frontend-lint.yml @@ -6,6 +6,10 @@ on: - main - release-*.*.* +permissions: + contents: read + id-token: write + jobs: lint-frontend-verify-i18n: name: Verify i18n @@ -30,6 +34,9 @@ jobs: exit 1 fi lint-frontend-prettier: + # Run this workflow only for PRs from forks; if it gets merged into `main` or `release-*`, + # the `lint-frontend-prettier-enterprise` workflow will run instead + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true name: Lint runs-on: ubuntu-latest steps: @@ -42,7 +49,29 @@ jobs: - run: yarn install --immutable --check-cache - run: yarn run prettier:check - run: yarn run lint + lint-frontend-prettier-enterprise: + # Run this workflow for non-PR events (like pushes to `main` or `release-*`) OR for internal PRs (PRs not from forks) + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + cache-dependency-path: 'yarn.lock' + - name: Setup Enterprise + uses: ./.github/actions/setup-enterprise + with: + github-app-name: 'grafana-ci-bot' + - run: yarn install --immutable --check-cache + - run: yarn run prettier:check + - run: yarn run lint lint-frontend-typecheck: + # Run this workflow only for PRs from forks; if it gets merged into `main` or `release-*`, + # the `lint-frontend-typecheck-enterprise` workflow will run instead + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true name: Typecheck runs-on: ubuntu-latest steps: @@ -54,6 +83,24 @@ jobs: cache-dependency-path: 'yarn.lock' - run: yarn install --immutable --check-cache - run: yarn run typecheck + lint-frontend-typecheck-enterprise: + # Run this workflow for non-PR events (like pushes to `main` or `release-*`) OR for internal PRs (PRs not from forks) + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false + name: Typecheck + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + cache-dependency-path: 'yarn.lock' + - name: Setup Enterprise + uses: ./.github/actions/setup-enterprise + with: + github-app-name: 'grafana-ci-bot' + - run: yarn install --immutable --check-cache + - run: yarn run typecheck lint-frontend-betterer: name: Betterer runs-on: ubuntu-latest diff --git a/.github/workflows/pr-frontend-unit-tests.yml b/.github/workflows/pr-frontend-unit-tests.yml index f3c61032d52..beced0ac371 100644 --- a/.github/workflows/pr-frontend-unit-tests.yml +++ b/.github/workflows/pr-frontend-unit-tests.yml @@ -6,8 +6,15 @@ on: - main - release-*.*.* +permissions: + contents: read + id-token: write + jobs: frontend-unit-tests: + # Run this workflow only for PRs from forks; if it gets merged into `main` or `release-*`, + # the `frontend-unit-tests-enterprise` workflow will run instead + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true runs-on: ubuntu-latest-8-cores name: "Unit tests (${{ matrix.chunk }} / 8)" strategy: @@ -27,3 +34,30 @@ jobs: TEST_MAX_WORKERS: 2 TEST_SHARD: ${{ matrix.chunk }} TEST_SHARD_TOTAL: 8 + + frontend-unit-tests-enterprise: + # Run this workflow for non-PR events (like pushes to `main` or `release-*`) OR for internal PRs (PRs not from forks) + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false + runs-on: ubuntu-latest-8-cores + name: "Unit tests (${{ matrix.chunk }} / 8)" + strategy: + fail-fast: false + matrix: + chunk: [1, 2, 3, 4, 5, 6, 7, 8] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + cache-dependency-path: 'yarn.lock' + - name: Setup Enterprise + uses: ./.github/actions/setup-enterprise + with: + github-app-name: 'grafana-ci-bot' + - run: yarn install --immutable --check-cache + - run: yarn run test:ci + env: + TEST_MAX_WORKERS: 2 + TEST_SHARD: ${{ matrix.chunk }} + TEST_SHARD_TOTAL: 8 From 72ebf1789e3dd0e0b36304db2b21ecdbf211bc6f Mon Sep 17 00:00:00 2001 From: "grafana-pr-automation[bot]" <140550294+grafana-pr-automation[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 09:45:47 +0100 Subject: [PATCH 109/146] I18n: Download translations from Crowdin (#104506) * New Crowdin translations by GitHub Action * kick CI --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Ashley Harrison --- public/locales/cs-CZ/grafana.json | 3 --- public/locales/de-DE/grafana.json | 3 --- public/locales/es-ES/grafana.json | 3 --- public/locales/fr-FR/grafana.json | 3 --- public/locales/hu-HU/grafana.json | 3 --- public/locales/id-ID/grafana.json | 3 --- public/locales/it-IT/grafana.json | 3 --- public/locales/ja-JP/grafana.json | 3 --- public/locales/ko-KR/grafana.json | 3 --- public/locales/nl-NL/grafana.json | 3 --- public/locales/pl-PL/grafana.json | 3 --- public/locales/pt-BR/grafana.json | 3 --- public/locales/pt-PT/grafana.json | 3 --- public/locales/ru-RU/grafana.json | 3 --- public/locales/sv-SE/grafana.json | 3 --- public/locales/tr-TR/grafana.json | 3 --- public/locales/zh-Hans/grafana.json | 3 --- public/locales/zh-Hant/grafana.json | 3 --- 18 files changed, 54 deletions(-) diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index 24c09600bbd..f2b73f0cced 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -2716,9 +2716,6 @@ "clear": "Vymazat vyhledávání a filtry", "text": "Nebyly nalezeny žádné výsledky pro váš dotaz" }, - "soft-delete": { - "success": "Nástěnka {{name}} byla přesunuta do Nedávno odstraněné" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index ee7c94e1abe..1ad178e3d4f 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -2680,9 +2680,6 @@ "clear": "Suche und Filter löschen", "text": "Keine Ergebnisse für Ihre Abfrage gefunden" }, - "soft-delete": { - "success": "Dashboard {{name}} in „Kürzlich gelöscht“ verschoben" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index 9f6e982821d..72aae2d0ed0 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -2680,9 +2680,6 @@ "clear": "Borrar la búsqueda y los filtros", "text": "No se han encontrado resultados para tu consulta" }, - "soft-delete": { - "success": "El panel de control {{name}} se ha movido a Eliminados recientemente" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 379c2d5b74a..c1affc0b43b 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -2680,9 +2680,6 @@ "clear": "Effacer la recherche et les filtres", "text": "Aucun résultat n'a été trouvé pour votre requête" }, - "soft-delete": { - "success": "Le tableau de bord {{name}} a été déplacé vers Récemment supprimé" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index b45d05edbaf..389f3e8a2ba 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -2680,9 +2680,6 @@ "clear": "Keresés és szűrők törlése", "text": "Nincs találat a lekérdezésre" }, - "soft-delete": { - "success": "A(z) {{name}} irányítópult áthelyezve a Nemrég töröltek közé" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index 36758020048..70da04320eb 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -2662,9 +2662,6 @@ "clear": "Hapus pencarian dan filter", "text": "Hasil untuk kueri Anda tidak ditemukan" }, - "soft-delete": { - "success": "Dasbor {{name}} dipindahkan ke Baru dihapus" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index 2c22ca5c261..e55529eedb2 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -2680,9 +2680,6 @@ "clear": "Cancella ricerca e filtri", "text": "Nessun risultato trovato per la ricerca" }, - "soft-delete": { - "success": "Dashboard {{name}} spostato in Eliminati di recente" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index 0df6cfcf16c..de4771b9926 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -2662,9 +2662,6 @@ "clear": "検索とフィルタをクリア", "text": "クエリに一致する結果が見つかりませんでした。" }, - "soft-delete": { - "success": "ダッシュボード、{{name}}が最近削除されたに移動しました" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index e1b16792990..3af7b1237ed 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -2662,9 +2662,6 @@ "clear": "검색 및 필터 초기화", "text": "쿼리에 대해 찾은 결과 없음" }, - "soft-delete": { - "success": "{{name}} 대시보드를 '최근 삭제됨'으로 이동함" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index 25c1c09308c..609ea743ded 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -2680,9 +2680,6 @@ "clear": "Zoekopdracht en filters wissen", "text": "Geen resultaten gevonden voor je zoekopdracht" }, - "soft-delete": { - "success": "Dashboard {{name}} is verplaatst naar Onlangs verwijderd" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index 6fdc8481bbe..43c7a1c70ca 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -2716,9 +2716,6 @@ "clear": "Wyczyść wyszukiwanie i filtry", "text": "Nie znaleziono wyników dla tego zapytania" }, - "soft-delete": { - "success": "Panel {{name}} przeniesiono do sekcji Ostatnio usunięte" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index cab0c1b91cb..dbf76560d65 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -2680,9 +2680,6 @@ "clear": "Limpar busca e filtros", "text": "Nenhum resultado encontrado para sua consulta" }, - "soft-delete": { - "success": "Painel de controle {{name}} movido para a seção \"Excluídos recentemente\"" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index af3cce19f54..565a52159e3 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -2680,9 +2680,6 @@ "clear": "Limpar a pesquisa e os filtros", "text": "Não foram encontrados resultados para a sua consulta" }, - "soft-delete": { - "success": "Painel de controlo {{name}} movido para Eliminados recentemente" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index 8c1dd0a7450..14b7b36c5a7 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -2716,9 +2716,6 @@ "clear": "Очистить поиск и фильтры", "text": "По вашему запросу ничего не найдено" }, - "soft-delete": { - "success": "Дашборд {{name}} перемещен в последние удаленные" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index 190031535bd..a9596c05382 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -2680,9 +2680,6 @@ "clear": "Rensa sökning och filter", "text": "Inga resultat hittades för din fråga" }, - "soft-delete": { - "success": "Instrumentpanelen {{name}} har flyttats till Nyligen raderat" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index 769660b1aa5..76e90cd6df0 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -2680,9 +2680,6 @@ "clear": "Aramayı ve filtreleri temizle", "text": "Sorgunuz için sonuç bulunamadı" }, - "soft-delete": { - "success": "{{name}} panosu Son silinenler'e taşındı" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 23dd1d2468c..97c652f3dc2 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -2662,9 +2662,6 @@ "clear": "清除搜索和筛选条件", "text": "未找到与您的查询相关的结果" }, - "soft-delete": { - "success": "数据面板 {{name}} 已移至“最近删除”" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index b73944a5257..29ae087b7aa 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -2662,9 +2662,6 @@ "clear": "清除搜尋和篩選條件", "text": "未找到您的查詢結果" }, - "soft-delete": { - "success": "儀表板「{{name}}」已移至「最近刪除」" - }, "text-this-repository-is-read-only": "" }, "canvas": { From 29b3738bc8c4920af4ce0ad1cf92152c4a4ad83b Mon Sep 17 00:00:00 2001 From: Matthew Thorning Date: Fri, 25 Apr 2025 09:49:32 +0100 Subject: [PATCH 110/146] Navigation: Remove the "New" badge from the IRM menu item (#104512) remove the "New" badge from the IRM menu item --- pkg/services/navtree/navtreeimpl/applinks.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/services/navtree/navtreeimpl/applinks.go b/pkg/services/navtree/navtreeimpl/applinks.go index 37ece2ee8fe..9b482ce58ec 100644 --- a/pkg/services/navtree/navtreeimpl/applinks.go +++ b/pkg/services/navtree/navtreeimpl/applinks.go @@ -262,9 +262,6 @@ func (s *ServiceImpl) addPluginToSection(c *contextmodel.ReqContext, treeRoot *n alertsAndIncidentsChildren = append(alertsAndIncidentsChildren, alertingNode) treeRoot.RemoveSection(alertingNode) } - if appLink.Id == "plugin-page-grafana-irm-app" { - appLink.IsNew = true - } alertsAndIncidentsChildren = append(alertsAndIncidentsChildren, appLink) treeRoot.AddSection(&navtree.NavLink{ Text: "Alerts & IRM", From 7b492d7e1610da99460cb121ec542c5b755fe6d1 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Fri, 25 Apr 2025 12:24:25 +0300 Subject: [PATCH 111/146] FEMT: Add feature toggle and expose the service in regular grafana (#104428) --- .../src/types/featureToggles.gen.ts | 4 + pkg/api/api.go | 9 ++ pkg/middleware/csp.go | 4 +- pkg/server/module_server.go | 22 +++- pkg/services/featuremgmt/registry.go | 8 +- pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 + pkg/services/featuremgmt/toggles_gen.json | 25 ++++ pkg/services/frontend/frontend_service.go | 51 +++------ pkg/services/frontend/index.go | 108 ++++++++++++++++++ pkg/services/frontend/index.html | 33 ++++++ 11 files changed, 225 insertions(+), 44 deletions(-) create mode 100644 pkg/services/frontend/index.go create mode 100644 pkg/services/frontend/index.html diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index c86709ef862..513f38f4fdb 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -1014,6 +1014,10 @@ export interface FeatureToggles { */ pluginsAutoUpdate?: boolean; /** + * Register MT frontend + */ + multiTenantFrontend?: boolean; + /** * Enables the alerting list view v2 preview toggle */ alertingListViewV2PreviewToggle?: boolean; diff --git a/pkg/api/api.go b/pkg/api/api.go index 14218d00299..fbcded89e6d 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -48,6 +48,7 @@ import ( "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/frontend" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol" publicdashboardsapi "github.com/grafana/grafana/pkg/services/publicdashboards/api" @@ -85,6 +86,14 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/login", hs.LoginView) r.Get("/invite/:code", hs.Index) + if hs.Features.IsEnabledGlobally(featuremgmt.FlagMultiTenantFrontend) { + index, err := frontend.NewIndexProvider(hs.Cfg, hs.License) + if err != nil { + panic(err) // ??? + } + r.Get("/mtfe", index.HandleRequest) + } + // authed views r.Get("/", reqSignedIn, hs.Index) r.Get("/profile/", reqSignedInNoAnonymous, hs.Index) diff --git a/pkg/middleware/csp.go b/pkg/middleware/csp.go index ebfd54c4450..c8c41a9468f 100644 --- a/pkg/middleware/csp.go +++ b/pkg/middleware/csp.go @@ -31,7 +31,7 @@ func ContentSecurityPolicy(cfg *setting.Cfg, logger log.Logger) func(http.Handle func nonceMiddleware(next http.Handler, logger log.Logger) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { ctx := contexthandler.FromContext(req.Context()) - nonce, err := generateNonce() + nonce, err := GenerateNonce() if err != nil { logger.Error("Failed to generate CSP nonce", "err", err) ctx.JsonApiErr(500, "Failed to generate CSP nonce", err) @@ -68,7 +68,7 @@ func ReplacePolicyVariables(policyTemplate, appURL, nonce string) string { return policy } -func generateNonce() (string, error) { +func GenerateNonce() (string, error) { var buf [16]byte if _, err := io.ReadFull(rand.Reader, buf[:]); err != nil { return "", err diff --git a/pkg/server/module_server.go b/pkg/server/module_server.go index 7142b4b8c63..defea7e9a08 100644 --- a/pkg/server/module_server.go +++ b/pkg/server/module_server.go @@ -9,6 +9,8 @@ import ( "strconv" "sync" + "github.com/prometheus/client_golang/prometheus" + "github.com/grafana/dskit/services" "github.com/grafana/grafana/pkg/api" "github.com/grafana/grafana/pkg/infra/log" @@ -16,16 +18,24 @@ import ( "github.com/grafana/grafana/pkg/services/authz" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/frontend" + "github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/sql" - "github.com/prometheus/client_golang/prometheus" ) // NewModule returns an instance of a ModuleServer, responsible for managing // dskit modules (services). -func NewModule(opts Options, apiOpts api.ServerOptions, features featuremgmt.FeatureToggles, cfg *setting.Cfg, storageMetrics *resource.StorageMetrics, indexMetrics *resource.BleveIndexMetrics, promGatherer prometheus.Gatherer) (*ModuleServer, error) { - s, err := newModuleServer(opts, apiOpts, features, cfg, storageMetrics, indexMetrics, promGatherer) +func NewModule(opts Options, + apiOpts api.ServerOptions, + features featuremgmt.FeatureToggles, + cfg *setting.Cfg, + storageMetrics *resource.StorageMetrics, + indexMetrics *resource.BleveIndexMetrics, + promGatherer prometheus.Gatherer, + license licensing.Licensing, +) (*ModuleServer, error) { + s, err := newModuleServer(opts, apiOpts, features, cfg, storageMetrics, indexMetrics, promGatherer, license) if err != nil { return nil, err } @@ -37,7 +47,7 @@ func NewModule(opts Options, apiOpts api.ServerOptions, features featuremgmt.Fea return s, nil } -func newModuleServer(opts Options, apiOpts api.ServerOptions, features featuremgmt.FeatureToggles, cfg *setting.Cfg, storageMetrics *resource.StorageMetrics, indexMetrics *resource.BleveIndexMetrics, promGatherer prometheus.Gatherer) (*ModuleServer, error) { +func newModuleServer(opts Options, apiOpts api.ServerOptions, features featuremgmt.FeatureToggles, cfg *setting.Cfg, storageMetrics *resource.StorageMetrics, indexMetrics *resource.BleveIndexMetrics, promGatherer prometheus.Gatherer, license licensing.Licensing) (*ModuleServer, error) { rootCtx, shutdownFn := context.WithCancel(context.Background()) s := &ModuleServer{ @@ -56,6 +66,7 @@ func newModuleServer(opts Options, apiOpts api.ServerOptions, features featuremg storageMetrics: storageMetrics, indexMetrics: indexMetrics, promGatherer: promGatherer, + license: license, } return s, nil @@ -79,6 +90,7 @@ type ModuleServer struct { mtx sync.Mutex storageMetrics *resource.StorageMetrics indexMetrics *resource.BleveIndexMetrics + license licensing.Licensing pidFile string version string @@ -153,7 +165,7 @@ func (s *ModuleServer) Run() error { }) m.RegisterModule(modules.FrontendServer, func() (services.Service, error) { - return frontend.ProvideFrontendService(s.cfg, s.promGatherer) + return frontend.ProvideFrontendService(s.cfg, s.promGatherer, s.license) }) m.RegisterModule(modules.All, nil) diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 682068ca1dc..0c70783efcf 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1738,13 +1738,19 @@ var ( FrontendOnly: true, }, { - Name: "pluginsAutoUpdate", Description: "Enables auto-updating of users installed plugins", Stage: FeatureStageExperimental, FrontendOnly: false, Owner: grafanaPluginsPlatformSquad, }, + { + Name: "multiTenantFrontend", + Description: "Register MT frontend", + Stage: FeatureStageExperimental, + FrontendOnly: false, + Owner: grafanaFrontendPlatformSquad, + }, { Name: "alertingListViewV2PreviewToggle", Description: "Enables the alerting list view v2 preview toggle", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index e29e7610d6e..9f6642437a7 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -228,6 +228,7 @@ unifiedNavbars,GA,@grafana/plugins-platform-backend,false,false,true logsPanelControls,preview,@grafana/observability-logs,false,false,true metricsFromProfiles,experimental,@grafana/observability-traces-and-profiling,false,false,true pluginsAutoUpdate,experimental,@grafana/plugins-platform-backend,false,false,false +multiTenantFrontend,experimental,@grafana/grafana-frontend-platform,false,false,false alertingListViewV2PreviewToggle,privatePreview,@grafana/alerting-squad,false,false,true alertRuleUseFiredAtForStartsAt,experimental,@grafana/alerting-squad,false,false,false alertingBulkActionsInUI,GA,@grafana/alerting-squad,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index c86dea8143f..4d577a49a4c 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -923,6 +923,10 @@ const ( // Enables auto-updating of users installed plugins FlagPluginsAutoUpdate = "pluginsAutoUpdate" + // FlagMultiTenantFrontend + // Register MT frontend + FlagMultiTenantFrontend = "multiTenantFrontend" + // FlagAlertingListViewV2PreviewToggle // Enables the alerting list view v2 preview toggle FlagAlertingListViewV2PreviewToggle = "alertingListViewV2PreviewToggle" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index c20b13fad3e..82f7b6df36b 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -1976,6 +1976,18 @@ "codeowner": "@grafana/alerting-squad" } }, + { + "metadata": { + "name": "multiTenantFrontend", + "resourceVersion": "1745438197175", + "creationTimestamp": "2025-04-23T19:56:37Z" + }, + "spec": { + "description": "Register MT frontend", + "stage": "experimental", + "codeowner": "@grafana/grafana-frontend-platform" + } + }, { "metadata": { "name": "multiTenantTempCredentials", @@ -1989,6 +2001,19 @@ "hideFromDocs": true } }, + { + "metadata": { + "name": "multitenantFrontend", + "resourceVersion": "1745438122785", + "creationTimestamp": "2025-04-23T19:55:22Z", + "deletionTimestamp": "2025-04-23T19:56:37Z" + }, + "spec": { + "description": "Register MT frontend", + "stage": "experimental", + "codeowner": "@grafana/grafana-frontend-platform" + } + }, { "metadata": { "name": "mysqlAnsiQuotes", diff --git a/pkg/services/frontend/frontend_service.go b/pkg/services/frontend/frontend_service.go index 3658d4097c0..81b67859ec2 100644 --- a/pkg/services/frontend/frontend_service.go +++ b/pkg/services/frontend/frontend_service.go @@ -6,11 +6,13 @@ import ( "net/http" "time" - "github.com/grafana/dskit/services" - "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/setting" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" + + "github.com/grafana/dskit/services" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/licensing" + "github.com/grafana/grafana/pkg/setting" ) type frontendService struct { @@ -20,13 +22,21 @@ type frontendService struct { log log.Logger errChan chan error promGatherer prometheus.Gatherer + + index *IndexProvider } -func ProvideFrontendService(cfg *setting.Cfg, promGatherer prometheus.Gatherer) (*frontendService, error) { +func ProvideFrontendService(cfg *setting.Cfg, promGatherer prometheus.Gatherer, license licensing.Licensing) (*frontendService, error) { + index, err := NewIndexProvider(cfg, license) + if err != nil { + return nil, err + } + s := &frontendService{ cfg: cfg, log: log.New("frontend-server"), promGatherer: promGatherer, + index: index, } s.BasicService = services.NewBasicService(s.start, s.running, s.stop) return s, nil @@ -64,7 +74,7 @@ func (s *frontendService) newFrontendServer(ctx context.Context) *http.Server { router := http.NewServeMux() router.Handle("/metrics", promhttp.HandlerFor(s.promGatherer, promhttp.HandlerOpts{EnableOpenMetrics: true})) - router.HandleFunc("/", s.handleRequest) + router.HandleFunc("/", s.index.HandleRequest) server := &http.Server{ // 5s timeout for header reads to avoid Slowloris attacks (https://thetooth.io/blog/slowloris-attack/) @@ -76,34 +86,3 @@ func (s *frontendService) newFrontendServer(ctx context.Context) *http.Server { return server } - -func (s *frontendService) handleRequest(writer http.ResponseWriter, request *http.Request) { - // This should: - // - get correct asset urls from fs or cdn - // - generate a nonce - // - render them into the index.html - // - and return it to the user! - - s.log.Info("handling request", "method", request.Method, "url", request.URL.String()) - htmlContent := ` - - - Grafana Frontend Server - - - -

Grafana Frontend Server

-

This is a simple static HTML page served by the Grafana frontend server module.

- -` - - writer.Header().Set("Content-Type", "text/html; charset=utf-8") - _, err := writer.Write([]byte(htmlContent)) - if err != nil { - s.log.Error("could not write to response", "err", err) - } -} diff --git a/pkg/services/frontend/index.go b/pkg/services/frontend/index.go new file mode 100644 index 00000000000..4f02bbe1127 --- /dev/null +++ b/pkg/services/frontend/index.go @@ -0,0 +1,108 @@ +package frontend + +import ( + "context" + "embed" + "errors" + "fmt" + "html/template" + "net/http" + "syscall" + + "github.com/grafana/grafana-app-sdk/logging" + "github.com/grafana/grafana/pkg/api/dtos" + "github.com/grafana/grafana/pkg/api/webassets" + "github.com/grafana/grafana/pkg/middleware" + "github.com/grafana/grafana/pkg/services/licensing" + "github.com/grafana/grafana/pkg/setting" +) + +type IndexProvider struct { + log logging.Logger + index *template.Template + data IndexViewData +} + +type IndexViewData struct { + CSPContent string + CSPEnabled bool + IsDevelopmentEnv bool + + AppSubUrl string + BuildVersion string + BuildCommit string + AppTitle string + + Assets *dtos.EntryPointAssets // Includes CDN info + + // Nonce is a cryptographic identifier for use with Content Security Policy. + Nonce string +} + +// Templates setup. +var ( + //go:embed *.html + templatesFS embed.FS + + // templates + htmlTemplates = template.Must(template.New("html").Delims("[[", "]]").ParseFS(templatesFS, `*.html`)) +) + +func NewIndexProvider(cfg *setting.Cfg, license licensing.Licensing) (*IndexProvider, error) { + assets, err := webassets.GetWebAssets(context.Background(), cfg, license) + if err != nil { + return nil, err + } + t := htmlTemplates.Lookup("index.html") + if t == nil { + return nil, fmt.Errorf("missing index template") + } + + return &IndexProvider{ + log: logging.DefaultLogger.With("logger", "index-provider"), + index: t, + data: IndexViewData{ + AppTitle: "Grafana", + AppSubUrl: cfg.AppSubURL, // Based on the request? + BuildVersion: cfg.BuildVersion, + BuildCommit: cfg.BuildCommit, + Assets: assets, + + CSPEnabled: cfg.CSPEnabled, + CSPContent: cfg.CSPTemplate, + + IsDevelopmentEnv: cfg.Env == setting.Dev, + }, + }, nil +} + +func (p *IndexProvider) HandleRequest(writer http.ResponseWriter, request *http.Request) { + if request.Method != "GET" { + writer.WriteHeader(http.StatusMethodNotAllowed) + return + } + + nonce, err := middleware.GenerateNonce() + if err != nil { + p.log.Error("error creating nonce", "err", err) + writer.WriteHeader(500) + return + } + + // TODO -- restructure so the static stuff is under one variable and the rest is dynamic + data := p.data // copy everything + data.Nonce = nonce + + if data.CSPEnabled { + data.CSPContent = middleware.ReplacePolicyVariables(p.data.CSPContent, p.data.AppSubUrl, data.Nonce) + } + + writer.Header().Set("Content-Type", "text/html; charset=UTF-8") + writer.WriteHeader(200) + if err := p.index.Execute(writer, &data); err != nil { + if errors.Is(err, syscall.EPIPE) { // Client has stopped listening. + return + } + panic(fmt.Sprintf("Error rendering index\n %s", err.Error())) + } +} diff --git a/pkg/services/frontend/index.html b/pkg/services/frontend/index.html new file mode 100644 index 00000000000..6778c160ca7 --- /dev/null +++ b/pkg/services/frontend/index.html @@ -0,0 +1,33 @@ + + + + [[ if and .CSPEnabled .IsDevelopmentEnv ]] + + + [[ end ]] + + + + + + [[.AppTitle]] + + + + + + [[range $asset := .Assets.CSSFiles]] + + [[end]] + + + + + + +

Grafana Frontend Server ([[.BuildVersion]])

+

This is a simple static HTML page served by the Grafana frontend server module.

+ + From eed048fc090f02c8ed22f40d04be2f90816d8f7c Mon Sep 17 00:00:00 2001 From: Michael Anderson <67813367+manderson-dev@users.noreply.github.com> Date: Fri, 25 Apr 2025 05:33:03 -0400 Subject: [PATCH 112/146] Navigation: Moving Machine-Learning out of IRM and into the top-level of the Navigation (#103822) * adding ml items to main navigation if plugin is installed * undoing testing change * updating based on feedback and fixing role to be specific to access to ml plugin * cleanup unneeded constants * cleanup diff * updateing GetOrgID call * adding greyscale ml logo and using that for consistency * use currentColor --------- Co-authored-by: Ashley Harrison --- packages/grafana-data/src/types/icon.ts | 1 + pkg/services/navtree/models.go | 1 + pkg/services/navtree/navtreeimpl/navtree.go | 80 +++++++++++++++++++++ public/img/icons/custom/gf-ml-alt.svg | 1 + 4 files changed, 83 insertions(+) create mode 100644 public/img/icons/custom/gf-ml-alt.svg diff --git a/packages/grafana-data/src/types/icon.ts b/packages/grafana-data/src/types/icon.ts index d3dea16ff5c..64d4bdd7a88 100644 --- a/packages/grafana-data/src/types/icon.ts +++ b/packages/grafana-data/src/types/icon.ts @@ -139,6 +139,7 @@ export const availableIconsIndex = { 'gf-layout-simple': true, 'gf-logs': true, 'gf-ml': true, + 'gf-ml-alt': true, 'gf-movepane-left': true, 'gf-movepane-right': true, 'gf-portrait': true, diff --git a/pkg/services/navtree/models.go b/pkg/services/navtree/models.go index 97d7ab1112b..5af7d6dbeb7 100644 --- a/pkg/services/navtree/models.go +++ b/pkg/services/navtree/models.go @@ -19,6 +19,7 @@ const ( WeightDrilldown WeightAlerting WeightAlertsAndIncidents + WeightAIAndML WeightTestingAndSynthetics WeightMonitoring WeightCloudServiceProviders diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go index fdc345a8026..0a1682b026c 100644 --- a/pkg/services/navtree/navtreeimpl/navtree.go +++ b/pkg/services/navtree/navtreeimpl/navtree.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/plugins" ac "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/apikey" "github.com/grafana/grafana/pkg/services/authn" @@ -17,6 +18,7 @@ import ( "github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/services/navtree" "github.com/grafana/grafana/pkg/services/org" + pc "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" pref "github.com/grafana/grafana/pkg/services/preference" @@ -161,6 +163,10 @@ func (s *ServiceImpl) GetNavTree(c *contextmodel.ReqContext, prefs *pref.Prefere if alertingSection := s.buildAlertNavLinks(c); alertingSection != nil { treeRoot.AddSection(alertingSection) } + + if aimlSection := s.buildAIMLNavLinks(c); aimlSection != nil { + treeRoot.AddSection(aimlSection) + } } if connectionsSection := s.buildDataConnectionsNavLink(c); connectionsSection != nil { @@ -416,6 +422,80 @@ func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext) []*navt return dashboardChildNavs } +func (s *ServiceImpl) buildAIMLNavLinks(c *contextmodel.ReqContext) *navtree.NavLink { + hasAccess := ac.HasAccess(s.accessControl, c) + + pss, err := s.pluginSettings.GetPluginSettings(c.Req.Context(), &pluginsettings.GetArgs{OrgID: c.GetOrgID()}) + if err != nil { + s.log.Error("Failed to get plugin settings", "error", err) + return nil + } + + // Check if ML plugin is enabled + isMLPluginEnabled := false + for _, plugin := range s.pluginStore.Plugins(c.Req.Context(), plugins.TypeApp) { + if plugin.ID == "grafana-ml-app" { + // Check if plugin is enabled in settings + if plugin.AutoEnabled { + isMLPluginEnabled = true + break + } + for _, ps := range pss { + if ps.PluginID == plugin.ID && ps.Enabled { + isMLPluginEnabled = true + break + } + } + break + } + } + + // Return nil if plugin is not enabled + if !isMLPluginEnabled { + return nil + } + + // Check if user has access to the plugin + if !hasAccess(ac.EvalPermission(pc.ActionAppAccess, "grafana-ml-app")) { + return nil + } + + var aimlChildNavs []*navtree.NavLink + + aimlChildNavs = append(aimlChildNavs, &navtree.NavLink{ + Text: "Metric forecasting", + SubTitle: "Create a forecast", + Id: "ai-ml-metric-forecast", + Url: s.cfg.AppSubURL + "/a/grafana-ml-app/metric-forecast", + }) + + aimlChildNavs = append(aimlChildNavs, &navtree.NavLink{ + Text: "Outlier detection", + SubTitle: "Create an outlier detector", + Id: "ai-ml-outlier-detection", + Url: s.cfg.AppSubURL + "/a/grafana-ml-app/outlier-detector", + }) + + aimlChildNavs = append(aimlChildNavs, &navtree.NavLink{ + Text: "Sift investigations", + SubTitle: "View and create investigations", + Id: "ai-ml-sift-investigations", + Url: s.cfg.AppSubURL + "/a/grafana-ml-app/investigations", + }) + + var aimlNav = navtree.NavLink{ + Text: "AI & Machine Learning", + SubTitle: "Explore AI and machine learning features", + Id: "ai-ml-home", + Icon: "gf-ml-alt", + Children: aimlChildNavs, + SortWeight: navtree.WeightAIAndML, + Url: s.cfg.AppSubURL + "/a/grafana-ml-app/home", + } + + return &aimlNav +} + func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext) *navtree.NavLink { hasAccess := ac.HasAccess(s.accessControl, c) var alertChildNavs []*navtree.NavLink diff --git a/public/img/icons/custom/gf-ml-alt.svg b/public/img/icons/custom/gf-ml-alt.svg new file mode 100644 index 00000000000..218d766af2e --- /dev/null +++ b/public/img/icons/custom/gf-ml-alt.svg @@ -0,0 +1 @@ + From 73c018b2986eec5518755adb8df640c11da71052 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Fri, 25 Apr 2025 12:50:03 +0300 Subject: [PATCH 113/146] Bootdata: Expose grafanaBootData under /bootdata URL (#104258) --- .../v0alpha1/dashboard_object_gen.go | 2 - .../dashboard/v1beta1/dashboard_object_gen.go | 2 - apps/dashboard/pkg/apis/dashboard_manifest.go | 2 - pkg/api/api.go | 3 ++ pkg/api/dtos/index.go | 52 +++++++++---------- pkg/api/frontendsettings.go | 12 +++++ 6 files changed, 41 insertions(+), 32 deletions(-) diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go b/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go index 50847df87c3..a267e0c8df8 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go @@ -294,8 +294,6 @@ var _ resource.ListObject = &DashboardList{} // Copy methods for all subresource types - - // DeepCopy creates a full deep copy of DashboardStatus func (s *DashboardStatus) DeepCopy() *DashboardStatus { cpy := &DashboardStatus{} diff --git a/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_object_gen.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_object_gen.go index 1423c7b0603..be021b5f003 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_object_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_object_gen.go @@ -294,8 +294,6 @@ var _ resource.ListObject = &DashboardList{} // Copy methods for all subresource types - - // DeepCopy creates a full deep copy of DashboardStatus func (s *DashboardStatus) DeepCopy() *DashboardStatus { cpy := &DashboardStatus{} diff --git a/apps/dashboard/pkg/apis/dashboard_manifest.go b/apps/dashboard/pkg/apis/dashboard_manifest.go index e8ebdfbc5ed..cc5451a78e8 100644 --- a/apps/dashboard/pkg/apis/dashboard_manifest.go +++ b/apps/dashboard/pkg/apis/dashboard_manifest.go @@ -11,8 +11,6 @@ import ( "github.com/grafana/grafana-app-sdk/app" ) -var () - var appManifestData = app.ManifestData{ AppName: "dashboard", Group: "dashboard.grafana.app", diff --git a/pkg/api/api.go b/pkg/api/api.go index fbcded89e6d..5503e367dfe 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -92,6 +92,9 @@ func (hs *HTTPServer) registerRoutes() { panic(err) // ??? } r.Get("/mtfe", index.HandleRequest) + + // Temporarily expose the full bootdata via API + r.Get("/bootdata", reqNoAuth, hs.GetBootdata) } // authed views diff --git a/pkg/api/dtos/index.go b/pkg/api/dtos/index.go index 6fa2d3766b8..2a55017e613 100644 --- a/pkg/api/dtos/index.go +++ b/pkg/api/dtos/index.go @@ -7,33 +7,33 @@ import ( ) type IndexViewData struct { - User *CurrentUser - Settings *FrontendSettingsDTO - AppUrl string - AppSubUrl string - GoogleAnalyticsId string - GoogleAnalytics4Id string - GoogleAnalytics4SendManualPageViews bool - GoogleTagManagerId string - NavTree *navtree.NavTreeRoot - BuildVersion string - BuildCommit string - ThemeType string - NewGrafanaVersionExists bool - NewGrafanaVersion string - AppName string - AppNameBodyClass string - FavIcon template.URL - AppleTouchIcon template.URL - AppTitle string - LoadingLogo template.URL - CSPContent string - CSPEnabled bool - IsDevelopmentEnv bool + User *CurrentUser `json:"user"` + Settings *FrontendSettingsDTO `json:"settings"` + AppUrl string `json:"-"` + AppSubUrl string `json:"-"` + GoogleAnalyticsId string `json:"-"` + GoogleAnalytics4Id string `json:"-"` + GoogleAnalytics4SendManualPageViews bool `json:"-"` + GoogleTagManagerId string `json:"-"` + NavTree *navtree.NavTreeRoot `json:"navtree"` + BuildVersion string `json:"-"` + BuildCommit string `json:"-"` + ThemeType string `json:"-"` + NewGrafanaVersionExists bool `json:"-"` + NewGrafanaVersion string `json:"-"` + AppName string `json:"-"` + AppNameBodyClass string `json:"-"` + FavIcon template.URL `json:"-"` + AppleTouchIcon template.URL `json:"-"` + AppTitle string `json:"-"` + LoadingLogo template.URL `json:"-"` + CSPContent string `json:"-"` + CSPEnabled bool `json:"-"` + IsDevelopmentEnv bool `json:"-"` // Nonce is a cryptographic identifier for use with Content Security Policy. - Nonce string - NewsFeedEnabled bool - Assets *EntryPointAssets // Includes CDN info + Nonce string `json:"-"` + NewsFeedEnabled bool `json:"-"` + Assets *EntryPointAssets `json:"assets"` // Includes CDN info } type EntryPointAssets struct { diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 935455a09c4..5333033d2bc 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -28,6 +28,18 @@ import ( "github.com/grafana/grafana/pkg/util" ) +// GetBootdataAPI returns the same data we currently have rendered into index.html +// NOTE: this should not be added to the public API docs, and is useful for a transition +// towards a fully static index.html -- this will likely be replaced with multiple calls +func (hs *HTTPServer) GetBootdata(c *contextmodel.ReqContext) { + data, err := hs.setIndexViewData(c) + if err != nil { + c.Handle(hs.Cfg, http.StatusInternalServerError, "Failed to get settings", err) + return + } + c.JSON(http.StatusOK, data) +} + // Returns a file that is easy to check for changes // Any changes to the file means we should refresh the frontend func (hs *HTTPServer) GetFrontendAssets(c *contextmodel.ReqContext) { From 3b036f0e785401c2d4fd8f487426cb58c7bd7d7e Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Fri, 25 Apr 2025 10:51:18 +0100 Subject: [PATCH 114/146] Graphite: Fix Graphite series interpolation (#104471) * Fix graphite series interpolation * Trigger build --- .../datasource/graphite/datasource.test.ts | 137 +++++++++++++----- .../plugins/datasource/graphite/datasource.ts | 28 ++-- 2 files changed, 119 insertions(+), 46 deletions(-) diff --git a/public/app/plugins/datasource/graphite/datasource.test.ts b/public/app/plugins/datasource/graphite/datasource.test.ts index 548797d7342..0ebe09c1c0b 100644 --- a/public/app/plugins/datasource/graphite/datasource.test.ts +++ b/public/app/plugins/datasource/graphite/datasource.test.ts @@ -5,9 +5,9 @@ import { createFetchResponse } from 'test/helpers/createFetchResponse'; import { AbstractLabelMatcher, AbstractLabelOperator, - getFrameDisplayName, - dateTime, DataQueryRequest, + dateTime, + getFrameDisplayName, MetricFindValue, } from '@grafana/data'; import { BackendSrvRequest } from '@grafana/runtime'; @@ -373,62 +373,116 @@ describe('graphiteDatasource', () => { describe('building graphite params', () => { it('should return empty array if no targets', () => { - const results = ctx.ds.buildGraphiteParams({ - targets: [{}], - }); + const originalTargetMap = { A: '' }; + const results = ctx.ds.buildGraphiteParams( + { + targets: [{}], + }, + originalTargetMap + ); expect(results.length).toBe(0); }); it('should uri escape targets', () => { - const results = ctx.ds.buildGraphiteParams({ - targets: [{ target: 'prod1.{test,test2}' }, { target: 'prod2.count' }], - }); + const originalTargetMap = { + A: 'prod1.{test,test2}', + B: 'prod2.count', + }; + const results = ctx.ds.buildGraphiteParams( + { + targets: [{ target: 'prod1.{test,test2}' }, { target: 'prod2.count' }], + }, + originalTargetMap + ); expect(results).toContain('target=prod1.%7Btest%2Ctest2%7D'); }); it('should replace target placeholder', () => { - const results = ctx.ds.buildGraphiteParams({ - targets: [{ target: 'series1' }, { target: 'series2' }, { target: 'asPercent(#A,#B)' }], - }); + const originalTargetMap = { + A: 'series1', + B: 'series2', + C: 'asPercent(#A,#B)', + }; + const results = ctx.ds.buildGraphiteParams( + { + targets: [{ target: 'series1' }, { target: 'series2' }, { target: 'asPercent(#A,#B)' }], + }, + originalTargetMap + ); expect(results[2]).toBe('target=asPercent(series1%2Cseries2)'); }); it('should replace target placeholder for hidden series', () => { - const results = ctx.ds.buildGraphiteParams({ - targets: [ - { target: 'series1', hide: true }, - { target: 'sumSeries(#A)', hide: true }, - { target: 'asPercent(#A,#B)' }, - ], - }); + const originalTargetMap = { + A: 'series1', + B: 'sumSeries(#A)', + C: 'asPercent(#A,#B)', + }; + const results = ctx.ds.buildGraphiteParams( + { + targets: [ + { target: 'series1', hide: true }, + { target: 'sumSeries(#A)', hide: true }, + { target: 'asPercent(#A,#B)' }, + ], + }, + originalTargetMap + ); expect(results[0]).toBe('target=' + encodeURIComponent('asPercent(series1,sumSeries(series1))')); }); it('should replace target placeholder when nesting query references', () => { - const results = ctx.ds.buildGraphiteParams({ - targets: [{ target: 'series1' }, { target: 'sumSeries(#A)' }, { target: 'asPercent(#A,#B)' }], - }); + const originalTargetMap = { + A: 'series1', + B: 'sumSeries(#A)', + C: 'asPercent(#A,#B)', + }; + const results = ctx.ds.buildGraphiteParams( + { + targets: [{ target: 'series1' }, { target: 'sumSeries(#A)' }, { target: 'asPercent(#A,#B)' }], + }, + originalTargetMap + ); expect(results[2]).toBe('target=' + encodeURIComponent('asPercent(series1,sumSeries(series1))')); }); it('should fix wrong minute interval parameters', () => { - const results = ctx.ds.buildGraphiteParams({ - targets: [{ target: "summarize(prod.25m.count, '25m', 'sum')" }], - }); + const originalTargetMap = { + A: "summarize(prod.25m.count, '25m', 'sum')", + }; + const results = ctx.ds.buildGraphiteParams( + { + targets: [{ target: "summarize(prod.25m.count, '25m', 'sum')" }], + }, + originalTargetMap + ); expect(results[0]).toBe('target=' + encodeURIComponent("summarize(prod.25m.count, '25min', 'sum')")); }); it('should fix wrong month interval parameters', () => { - const results = ctx.ds.buildGraphiteParams({ - targets: [{ target: "summarize(prod.5M.count, '5M', 'sum')" }], - }); + const originalTargetMap = { + A: "summarize(prod.5M.count, '5M', 'sum')", + }; + const results = ctx.ds.buildGraphiteParams( + { + targets: [{ target: "summarize(prod.5M.count, '5M', 'sum')" }], + }, + originalTargetMap + ); expect(results[0]).toBe('target=' + encodeURIComponent("summarize(prod.5M.count, '5mon', 'sum')")); }); it('should ignore empty targets', () => { - const results = ctx.ds.buildGraphiteParams({ - targets: [{ target: 'series1' }, { target: '' }], - }); + const originalTargetMap = { + A: 'series1', + B: '', + }; + const results = ctx.ds.buildGraphiteParams( + { + targets: [{ target: 'series1' }, { target: '' }], + }, + originalTargetMap + ); expect(results.length).toBe(2); }); @@ -442,9 +496,15 @@ describe('graphiteDatasource', () => { }, ]); - const results = ctx.ds.buildGraphiteParams({ - targets: [{ target: 'my.$metric.*' }], - }); + const originalTargetMap = { + A: 'my.$metric.*', + }; + const results = ctx.ds.buildGraphiteParams( + { + targets: [{ target: 'my.$metric.*' }], + }, + originalTargetMap + ); expect(results).toStrictEqual(['target=my.b.*', 'format=json']); }); @@ -456,10 +516,13 @@ describe('graphiteDatasource', () => { current: { value: ['a', 'b'] }, }, ]); - - const results = ctx.ds.buildGraphiteParams({ - targets: [{ target: 'my.[[metric]].*' }], - }); + const originalTargetMap = { A: 'my.[[metric]].*' }; + const results = ctx.ds.buildGraphiteParams( + { + targets: [{ target: 'my.[[metric]].*' }], + }, + originalTargetMap + ); expect(results).toStrictEqual(['target=my.%7Ba%2Cb%7D.*', 'format=json']); }); diff --git a/public/app/plugins/datasource/graphite/datasource.ts b/public/app/plugins/datasource/graphite/datasource.ts index e8a6487a275..52e5b0ebc62 100644 --- a/public/app/plugins/datasource/graphite/datasource.ts +++ b/public/app/plugins/datasource/graphite/datasource.ts @@ -1,4 +1,4 @@ -import { each, indexOf, isArray, isString, map as _map } from 'lodash'; +import { map as _map, each, indexOf, isArray, isString } from 'lodash'; import { lastValueFrom, merge, Observable, of, OperatorFunction, pipe, throwError } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; @@ -13,13 +13,13 @@ import { DataSourceWithQueryExportSupport, dateMath, dateTime, + getSearchFilterScopedVar, MetricFindValue, QueryResultMetaStat, ScopedVars, TimeRange, TimeZone, toDataFrame, - getSearchFilterScopedVar, } from '@grafana/data'; import { BackendSrvRequest, FetchResponse, getBackendSrv } from '@grafana/runtime'; import { isVersionGtOrEq, SemVersion } from 'app/core/utils/version'; @@ -210,12 +210,19 @@ export class GraphiteDatasource return merge(...streams); } - // Use this object to map the original refID of the query to our sanitised one - const refIds: { [key: string]: string } = {}; + // Use this object to map the sanitised refID to the original + const formattedRefIdsMap: { [key: string]: string } = {}; + // Use this object to map the original refID to the original target + const originalTargetMap: { [key: string]: string } = {}; for (const target of options.targets) { // Sanitise the refID otherwise the Graphite query will fail const formattedRefId = target.refId.replaceAll(' ', '_'); - refIds[formattedRefId] = target.refId; + formattedRefIdsMap[formattedRefId] = target.refId; + // Track the original target to ensure if we need to interpolate a series, we interpolate using the original target + // rather than the target wrapped in aliasSub e.g.: + // Suppose a query has three targets: A: metric1 B: sumSeries(#A) and C: asPercent(#A, #B) + // We want the targets to be interpolated to: A: aliasSub(metric1, "(^.*$)", "\\1 A"), B: aliasSub(sumSeries(metric1), "(^.*$)", "\\1 B") and C: asPercent(metric1, sumSeries(metric1)) + originalTargetMap[target.refId] = target.target || ''; // Use aliasSub to include the refID in the response series name. This allows us to set the refID on the frame. const updatedTarget = `aliasSub(${target.target}, "(^.*$)", "\\1 ${formattedRefId}")`; target.target = updatedTarget; @@ -231,7 +238,7 @@ export class GraphiteDatasource maxDataPoints: options.maxDataPoints, }; - const params = this.buildGraphiteParams(graphOptions, options.scopedVars); + const params = this.buildGraphiteParams(graphOptions, originalTargetMap, options.scopedVars); if (params.length === 0) { return of({ data: [] }); } @@ -255,7 +262,9 @@ export class GraphiteDatasource httpOptions.requestId = this.name + '.panelId.' + options.panelId; } - return this.doGraphiteRequest(httpOptions).pipe(map((result) => this.convertResponseToDataFrames(result, refIds))); + return this.doGraphiteRequest(httpOptions).pipe( + map((result) => this.convertResponseToDataFrames(result, formattedRefIdsMap)) + ); } addTracingHeaders( @@ -975,7 +984,7 @@ export class GraphiteDatasource ); } - buildGraphiteParams(options: any, scopedVars?: ScopedVars): string[] { + buildGraphiteParams(options: any, originalTargetMap: { [key: string]: string }, scopedVars?: ScopedVars): string[] { const graphiteOptions = ['from', 'until', 'rawData', 'format', 'maxDataPoints', 'cacheTimeout']; const cleanOptions = [], targets: Record = {}; @@ -1006,7 +1015,8 @@ export class GraphiteDatasource } function nestedSeriesRegexReplacer(match: string, g1: string | number) { - return targets[g1] || match; + // Recursively replace all nested series references + return originalTargetMap[g1].replace(regex, nestedSeriesRegexReplacer) || match; } for (i = 0; i < options.targets.length; i++) { From 40799d1f57afccaf4210a15952f856c35a2f7bc6 Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Fri, 25 Apr 2025 05:06:12 -0600 Subject: [PATCH 115/146] Docs: Add dashboard and folder apis docs (#101754) --- docs/sources/developers/http_api/apis.md | 99 ++ docs/sources/developers/http_api/dashboard.md | 851 +++++++++++++++++- docs/sources/developers/http_api/folder.md | 389 +++++++- .../setup-grafana/configure-grafana/_index.md | 17 + 4 files changed, 1339 insertions(+), 17 deletions(-) create mode 100644 docs/sources/developers/http_api/apis.md diff --git a/docs/sources/developers/http_api/apis.md b/docs/sources/developers/http_api/apis.md new file mode 100644 index 00000000000..c00d34b4de7 --- /dev/null +++ b/docs/sources/developers/http_api/apis.md @@ -0,0 +1,99 @@ +--- +aliases: + - ../../http_api/new_api_structure/ + - ../../http_api/new_api_structure/ +canonical: /docs/grafana/latest/developers/http_api/new_api_structure/ +description: '' +keywords: + - grafana + - http + - documentation + - api +labels: + products: + - enterprise + - oss +title: New API Structure +--- + +# Grafana's New API Structure + +## Overview + +Going forward, Grafana's HTTP API will follow a standardized API structure alongside consistent API versioning. + +## API Path Structure + +All Grafana APIs follow this standardized format: + +``` +/apis///namespaces//[/] +``` + +Where the final `/` segment is used for operations on individual resources (like Get, Update, Delete) and omitted for collection operations (like List, Create). + +## Understanding the Components + +### Group (``) + +Groups organize related functionality into logical collections. For example `dashboard.grafana.app` will be used for all dashboard-related operations. + +### Version (``) + +These APIs will also uses semantic versioning with three stability levels: + +| Level | Format | Description | Use Case | +| ----- | ---------- | --------------------------------------------------------------------------- | ------------------------ | +| Alpha | `v1alpha1` | Early development stage. Unstable, may contain bugs, and subject to removal | For testing new features | +| Beta | `v1beta1` | More stable than alpha, but may still have some changes | For early production use | +| GA | `v1` | Generally Available. Stable with backward compatibility guarantees | For production use | + +### Namespace (``) + +Namespaces isolate resources within your Grafana instance. The format varies by deployment type: + +#### OSS & On-Premise Grafana + +- Default organization (org 1): `default` +- Additional organizations: `org-` + +#### Grafana Cloud + +- Format: `stacks-` +- Your instance ID is the `stack_id`. You can find this value by either: + - Going to grafana.com, clicking on your stack, and selecting "Details" on your Grafana instance + - Accessing the /swagger page in your cloud instance, where the namespace will be automatically populated on the relevant endpoints + +### Resource (``) + +Represents the core resource you want to interact with, such as: + +- `dashboards` +- `playlists` +- `folders` + +### Name () + +The `` is the unique identifier for a specific instance of a resource within its namespace and resource type. `` is distinct from the metadata.uid field. The URL path will always use the metadata.name. + +For example, to get a dashboard defined as: + +``` +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v1beta1", + "metadata": { + "name": "production-overview", // This value IS used in the URL path + "namespace": "default", + "uid": "a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8" // This value is NOT used in the URL path + // ... other metadata + }, + "spec": { + // ... dashboard spec + } +} +``` + +You would use the following API call: + +`GET /apis/dashboard.grafana.app/v1beta1/namespaces/default/dashboards/production-overview` diff --git a/docs/sources/developers/http_api/dashboard.md b/docs/sources/developers/http_api/dashboard.md index d3e0a61242f..2dfe6322441 100644 --- a/docs/sources/developers/http_api/dashboard.md +++ b/docs/sources/developers/http_api/dashboard.md @@ -16,22 +16,859 @@ labels: title: Dashboard HTTP API --- -# Dashboard API +# New Dashboard APIs > If you are running Grafana Enterprise, for some endpoints you'll need to have specific permissions. Refer to [Role-based access control permissions](/docs/grafana/latest/administration/roles-and-permissions/access-control/custom-role-actions-scopes/) for more information. -## Identifier (id) vs unique identifier (uid) +> To view more about the new api structure, refer to [API overview]({{< ref "apis" >}}). -The identifier (id) of a dashboard is an auto-incrementing numeric value and is only unique per Grafana install. +## Create Dashboard -The unique identifier (uid) of a dashboard can be used for uniquely identify a dashboard between multiple Grafana installs. +`POST /apis/dashboard.grafana.app/v1beta1/namespaces/:namespace/dashboards` + +Creates a new dashboard. + +- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}). + +**Required permissions** + +See note in the [introduction]({{< ref "#dashboard-api" >}}) for an explanation. + + +| Action | Scope | +| ------------------- | ------------------------------------------------------------------------------------------------------- | +| `dashboards:create` |
  • `folders:*`
  • `folders:uid:*`
| +| `dashboards:write` |
  • `dashboards:*`
  • `dashboards:uid:*`
  • `folders:*`
  • `folders:uid:*`
| +{ .no-spacing-list } + + +**Example Create Request**: + +```http +POST /apis/dashboard.grafana.app/v1beta1/namespaces/default/dashboards HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +{ + "metadata": { + "name": "gdxccn", + "annotations": { + "grafana.app/folder": "fef30w4jaxla8b" + }, + }, + "spec": { + "annotations": { + "list": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": false, + "iconColor": "red", + "name": "Example annotation", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + } + }] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [ + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": false, + "title": "Example Link", + "tooltip": "", + "type": "dashboards", + "url": "" + } + ], + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "description": "With a description", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Example panel", + "type": "timeseries" + } + ], + "preload": false, + "schemaVersion": 41, + "tags": ["example"], + "templating": { + "list": [ + { + "current": { + "text": "", + "value": "" + }, + "definition": "", + "description": "example description", + "label": "ExampleLabel", + "name": "ExampleVariable", + "options": [], + "query": "", + "refresh": 1, + "regex": "cluster", + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Example Dashboard", + "version": 0 + } +} +``` + +JSON Body schema: + +- **metadata.name** – The Grafana [unique identifier]({{< ref "#identifier-id-vs-unique-identifier-uid" >}}). If you do not want to provide this, set metadata.generateName instead to the prefix you would like for the randomly generated uid (cannot be an empty string). +- **metadata.annotations.grafana.app/folder** - Optional field, the unique identifier of the folder under which the dashboard should be created. +- **spec** – The dashboard json. + +**Example Response**: + +```http +HTTP/1.1 200 OK +Content-Type: application/json; charset=UTF-8 +Content-Length: 485 + +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v1beta1", + "metadata": { + "name": "gdxccn", + "namespace": "default", + "uid": "Cc7fA5ffHY94NnHZyMxXvFlpFtOmkK3qkBcVZPKSPXcX", + "resourceVersion": "1", + "generation": 1, + "creationTimestamp": "2025-04-24T20:35:29Z", + "labels": { + "grafana.app/deprecatedInternalID": "11" + }, + "annotations": { + "grafana.app/createdBy": "service-account:dejwtrofg77y8d", + "grafana.app/folder": "fef30w4jaxla8b" + }, + "managedFields": [ + { + "manager": "curl", + "operation": "Update", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "time": "2025-04-24T20:35:29Z", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:spec": { + "f:annotations": { + ".": {}, + "f:list": {} + }, + "f:editable": {}, + "f:fiscalYearStartMonth": {}, + "f:graphTooltip": {}, + "f:links": {}, + "f:panels": {}, + "f:preload": {}, + "f:schemaVersion": {}, + "f:tags": {}, + "f:templating": { + ".": {}, + "f:list": {} + }, + "f:time": { + ".": {}, + "f:from": {}, + "f:to": {} + }, + "f:timepicker": {}, + "f:timezone": {}, + "f:title": {}, + "f:version": {} + } + } + } + ] + }, + "spec": { + "annotations": { + "list": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": false, + "iconColor": "red", + "name": "Example annotation", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + } + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [ + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": false, + "title": "Example Link", + "tooltip": "", + "type": "dashboards", + "url": "" + } + ], + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "description": "With a description", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Example panel", + "type": "timeseries" + } + ], + "preload": false, + "schemaVersion": 41, + "tags": [ + "example" + ], + "templating": { + "list": [ + { + "current": { + "text": "", + "value": "" + }, + "definition": "", + "description": "example description", + "label": "ExampleLabel", + "name": "ExampleVariable", + "options": [], + "query": "", + "refresh": 1, + "regex": "cluster", + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Example Dashboard" + }, + "status": {} +``` + +Status Codes: + +- **201** – Created +- **400** – Errors (invalid json, missing or invalid fields, etc) +- **401** – Unauthorized +- **403** – Access denied +- **409** – Conflict (dashboard with the same uid already exists) + +## Update Dashboard + +`PUT /apis/dashboard.grafana.app/v1beta1/namespaces/:namespace/dashboards/:uid` + +Updates an existing dashboard via the dashboard uid. + +- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}). +- uid: the unique identifier of the dashboard to update. this will be the _name_ in the dashboard response + +**Required permissions** + +See note in the [introduction]({{< ref "#dashboard-api" >}}) for an explanation. + + +| Action | Scope | +| ------------------- | ------------------------------------------------------------------------------------------------------- | +| `dashboards:write` |
  • `dashboards:*`
  • `dashboards:uid:*`
  • `folders:*`
  • `folders:uid:*`
| +{ .no-spacing-list } + + +**Example Update Request**: + +```http +POST /apis/dashboard.grafana.app/v1beta1/namespaces/default/dashboards/gdxccn HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +{ + "metadata": { + "name": "gdxccn", + "annotations": { + "grafana.app/folder": "fef30w4jaxla8b", + "grafana.app/message": "commit message" + }, + }, + "spec": { + "title": "New dashboard - updated", + "schemaVersion": 41, + ... + } +} +``` + +JSON Body schema: + +- **metadata.name** – The [unique identifier]({{< ref "#identifier-id-vs-unique-identifier-uid" >}}). +- **metadata.annotations.grafana.app/folder** - Optional field, the unique identifier of the folder under which the dashboard should be created. +- **metadata.annotations.grafana.app/message** - Optional field, to set a commit message for the version history. +- **spec** – The dashboard json. + +**Example Response**: + +```http +HTTP/1.1 200 OK +Content-Type: application/json; charset=UTF-8 +Content-Length: 485 + +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v1beta1", + "metadata": { + "name": "gdxccn", + "namespace": "default", + "uid": "Cc7fA5ffHY94NnHZyMxXvFlpFtOmkK3qkBcVZPKSPXcX", + "resourceVersion": "2", + "generation": 2, + "creationTimestamp": "2025-03-06T19:57:18Z", + "annotations": { + "grafana.app/folder": "fef30w4jaxla8b", + "grafana.app/createdBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedTimestamp": "2025-03-07T02:58:36Z" + } + }, + "spec": { + "schemaVersion": 41, + "title": "New dashboard - updated", + ... + } +} +``` + +Status Codes: + +- **200** – OK +- **400** – Errors (invalid json, missing or invalid fields, etc) +- **401** – Unauthorized +- **403** – Access denied +- **409** – Conflict (dashboard with the same version already exists) + +## Get Dashboard + +`GET /apis/dashboard.grafana.app/v1beta1/namespaces/:namespace/dashboards/:uid` + +Gets a dashboard via the dashboard uid. + +- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}). +- uid: the unique identifier of the dashboard to update. this will be the _name_ in the dashboard response + +Note: For large dashboards, add `/dto` to the end of the URL to get the full dashboard body. + +**Required permissions** + +See note in the [introduction]({{< ref "#dashboard-api" >}}) for an explanation. + + +| Action | Scope | +| ----------------- | ------------------------------------------------------------------------------------------------------- | +| `dashboards:read` |
  • `dashboards:*`
  • `dashboards:uid:*`
  • `folders:*`
  • `folders:uid:*`
| +{ .no-spacing-list } + + +**Example Get Request**: + +```http +GET /apis/dashboard.grafana.app/v1beta1/namespaces/default/dashboards/gdxccn HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk +``` + +**Example Response**: + +```http +HTTP/1.1 200 OK +Content-Type: application/json; charset=UTF-8 +Content-Length: 485 + +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v1beta1", + "metadata": { + "name": "gdxccn", + "namespace": "default", + "uid": "Cc7fA5ffHY94NnHZyMxXvFlpFtOmkK3qkBcVZPKSPXcX", + "resourceVersion": "2", + "generation": 2, + "creationTimestamp": "2025-03-06T19:57:18Z", + "annotations": { + "grafana.app/createdBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedTimestamp": "2025-03-07T02:58:36Z" + } + }, + "spec": { + "schemaVersion": 41, + "title": "New dashboard - updated", + ... + } +} +``` + +Status Codes: + +- **200** – OK +- **401** – Unauthorized +- **403** – Access denied +- **404** – Not Found + +## List Dashboards + +`GET /apis/dashboard.grafana.app/v1beta1/namespaces/:namespace/dashboards` + +Lists all dashboards in the given organization. You can control the maximum number of dashboards returned through the `limit` query parameter. You can then use the `continue` token returned to fetch the next page of dashboards. + +- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}). + +Note: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}). + +**Required permissions** + +See note in the [introduction]({{< ref "#dashboard-api" >}}) for an explanation. + + +| Action | Scope | +| ----------------- | ------------------------------------------------------------------------------------------------------- | +| `dashboards:read` |
  • `dashboards:*`
  • `dashboards:uid:*`
  • `folders:*`
  • `folders:uid:*`
| +{ .no-spacing-list } + + +**Example Get Request**: + +```http +GET /apis/dashboard.grafana.app/v1beta1/namespaces/default/dashboards?limit=1 HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk +``` + +**Example Response**: + +```http +HTTP/1.1 200 OK +Content-Type: application/json; charset=UTF-8 +Content-Length: 644 + +{ + "kind": "DashboardList", + "apiVersion": "dashboard.grafana.app/v1alpha1", + "metadata": { + "resourceVersion": "1741315830000", + "continue": "org:1/start:1158/folder:" + }, + "items": [ + { + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v1alpha1", + "metadata": { + "name": "gpqcmf", + "namespace": "default", + "uid": "VQyL7pNTpfGPNlPM6HRJSePrBg5dXmxr4iPQL7txLtwX", + "resourceVersion": "1", + "generation": 1, + "creationTimestamp": "2025-03-06T19:50:30Z", + "annotations": { + "grafana.app/createdBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedTimestamp": "2025-03-06T19:50:30Z" + } + }, + "spec": { + "schemaVersion": 41, + "title": "New dashboard", + "uid": "gpqcmf", + "version": 1, + ... + } + } + ] +} +``` + +Status Codes: + +- **200** – OK +- **401** – Unauthorized +- **403** – Access denied + +## Delete Dashboard + +`DELETE /apis/dashboard.grafana.app/v1beta1/namespaces/:namespace/dashboards/:uid` + +Deletes a dashboard via the dashboard uid. + +- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}). +- uid: the unique identifier of the dashboard to update. this will be the _name_ in the dashboard response + +**Required permissions** + +See note in the [introduction]({{< ref "#dashboard-api" >}}) for an explanation. + + +| Action | Scope | +| ------------------- | ------------------------------------------------------------------------------------------------------- | +| `dashboards:delete` |
  • `dashboards:*`
  • `dashboards:uid:*`
  • `folders:*`
  • `folders:uid:*`
| +{ .no-spacing-list } + + +**Example Delete Request**: + +```http +DELETE /apis/dashboard.grafana.app/v1beta1/namespaces/default/dashboards/gdxccn HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk +``` + +**Example Response**: + +```http +HTTP/1.1 200 OK +Content-Type: application/json; charset=UTF-8 +Content-Length: 78 + +{ + "kind": "Status", + "apiVersion": "v1", + "metadata": {}, + "status": "Success", + "details": { + "name": "gdxccn", + "group": "dashboard.grafana.app", + "kind": "dashboards", + "uid": "Cc7fA5ffHY94NnHZyMxXvFlpFtOmkK3qkBcVZPKSPXcX" + } +} +``` + +Status Codes: + +- **200** – OK +- **401** – Unauthorized +- **403** – Access denied +- **404** – Not found + +## Gets the home dashboard + +`GET /api/dashboards/home` + +Will return the home dashboard. + +**Example Request**: + +```http +GET /api/dashboards/home HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk +``` + +**Example Response**: + +```http +HTTP/1.1 200 +Content-Type: application/json + +{ + "dashboard": { + "editable":false, + "nav":[ + { + "enable":false, + "type":"timepicker" + } + ], + "style":"dark", + "tags":[], + "templating":{ + "list":[ + ] + }, + "time":{ + }, + "timezone":"browser", + "title":"Home", + "version":5 + }, + "meta": { + "isHome":true, + "canSave":false, + "canEdit":false, + "canStar":false, + "url":"", + "expires":"0001-01-01T00:00:00Z", + "created":"0001-01-01T00:00:00Z" + } +} +``` + +## Tags for Dashboard + +`GET /api/dashboards/tags` + +Get all tags of dashboards + +**Example Request**: + +```http +GET /api/dashboards/tags HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk +``` + +**Example Response**: + +```http +HTTP/1.1 200 +Content-Type: application/json + +[ + { + "term":"tag1", + "count":1 + }, + { + "term":"tag2", + "count":4 + } +] +``` + +## Dashboard Search + +See [Folder/Dashboard Search API]({{< relref "folder_dashboard_search/" >}}). + +## APIs + +### Unique identifier (uid) vs identifier (id) + +The unique identifier (uid) of a dashboard can be used to uniquely identify a dashboard within a given org. It's automatically generated if not provided when creating a dashboard. The uid allows having consistent URLs for accessing dashboards and when syncing dashboards between multiple Grafana installs, see [dashboard provisioning](/docs/grafana/latest/administration/provisioning/#dashboards) for more information. This means that changing the title of a dashboard will not break any bookmarked links to that dashboard. The uid can have a maximum length of 40 characters. -## Create / Update dashboard +The identifier (id) of a dashboard is deprecated in favor of the unique identifier (uid). + +### Create / Update dashboard `POST /api/dashboards/db` @@ -155,7 +992,7 @@ Content-Length: 97 } ``` -## Get dashboard by uid +### Get dashboard by uid `GET /api/dashboards/uid/:uid` @@ -214,7 +1051,7 @@ Status Codes: - **403** – Access denied - **404** – Not found -## Delete dashboard by uid +### Delete dashboard by uid `DELETE /api/dashboards/uid/:uid` diff --git a/docs/sources/developers/http_api/folder.md b/docs/sources/developers/http_api/folder.md index 7a65f7fa56d..6a8e2b1f771 100644 --- a/docs/sources/developers/http_api/folder.md +++ b/docs/sources/developers/http_api/folder.md @@ -16,19 +16,388 @@ labels: title: Folder HTTP API --- -# Folder API +# New Folders APIs > If you are running Grafana Enterprise, for some endpoints you'll need to have specific permissions. Refer to [Role-based access control permissions](/docs/grafana/latest/administration/roles-and-permissions/access-control/custom-role-actions-scopes/) for more information. +> To view more about the new api structure, refer to [API overview]({{< ref "apis" >}}). + +### Get all folders + +`GET /apis/folder.grafana.app/v1beta1/namespaces/:namespace/folders` + +Returns all folders that the authenticated user has permission to view within the given organization. Use the `limit` query parameter to control the maximum number of dashboards returned. To retrieve additional dashboards, utilize the `continue` token provided in the response to fetch the next page. + +- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}). + +**Required permissions** + +See note in the [introduction]({{< ref "#folder-api" >}}) for an explanation. + +| Action | Scope | +| -------------- | ----------- | +| `folders:read` | `folders:*` | + +**Example Request**: + +```http +GET /apis/folder.grafana.app/v1beta1/namespaces/default/folders?limit=1 HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk +``` + +**Example Response**: + +```http +HTTP/1.1 200 +Content-Type: application/json +{ + "kind": "FolderList", + "apiVersion": "folder.grafana.app/v1beta1", + "metadata": { + "continue": "org:1/start:1158/folder:" + }, + "items": [ + { + "kind": "Folder", + "apiVersion": "folder.grafana.app/v1beta1", + "metadata": { + "name": "aef30vrzxs3y8d", + "namespace": "default", + "uid": "KCtv1FXDsJmTYQoTgcPnfuwZhDZge3uMpXOefaOHjb4X", + "resourceVersion": "1741343686000", + "creationTimestamp": "2025-03-07T10:34:46Z", + "annotations": { + "grafana.app/createdBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedTimestamp": "2025-03-07T10:34:46Z" + } + }, + "spec": { + "title": "example" + } + } + ] +} +``` + +Status Codes: + +- **200** – OK +- **401** – Unauthorized +- **403** – Access Denied + +### Get folder by uid + +`GET /apis/folder.grafana.app/v1beta1/namespaces/:namespace/folders/:uid` + +Will return the folder given the folder uid. + +- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}). +- uid: the unique identifier of the folder to update. this will be the _name_ in the folder response + +**Required permissions** + +See note in the [introduction]({{< ref "#folder-api" >}}) for an explanation. + +| Action | Scope | +| -------------- | ----------- | +| `folders:read` | `folders:*` | + +**Example Request**: + +```http +GET /apis/folder.grafana.app/v1beta1/namespaces/default/folders/aef30vrzxs3y8d HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk +``` + +**Example Response**: + +```http +HTTP/1.1 200 +Content-Type: application/json +{ + "kind": "Folder", + "apiVersion": "folder.grafana.app/v1beta1", + "metadata": { + "name": "aef30vrzxs3y8d", + "namespace": "default", + "uid": "KCtv1FXDsJmTYQoTgcPnfuwZhDZge3uMpXOefaOHjb4X", + "resourceVersion": "1741343686000", + "creationTimestamp": "2025-03-07T10:34:46Z", + "annotations": { + "grafana.app/createdBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedTimestamp": "2025-03-07T10:34:46Z", + "grafana.app/folder": "fef30w4jaxla8b" + } + }, + "spec": { + "title": "test" + } +} +``` + +Note the annotation `grafana.app/folder` which contains the uid of the parent folder. + +Status Codes: + +- **200** – Found +- **401** – Unauthorized +- **403** – Access Denied +- **404** – Folder not found + +### Create folder + +`POST /apis/folder.grafana.app/v1beta1/namespaces/:namespace/folders` + +Creates a new folder. + +- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}). + +**Required permissions** + +See note in the [introduction]({{< ref "#folder-api" >}}) for an explanation. + +`folders:create` allows creating folders and subfolders. If granted with scope `folders:uid:general`, allows creating root level folders. Otherwise, allows creating subfolders under the specified folders. + +| Action | Scope | +| ---------------- | ----------- | +| `folders:create` | `folders:*` | +| `folders:write` | `folders:*` | + +**Example Request**: + +```http +POST /apis/folder.grafana.app/v1beta1/namespaces/default/folders HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +{ + "metadata": { + "name": "aef30vrzxs3y8d", + "annotations": { + "grafana.app/folder": "fef30w4jaxla8b" + } + } + "spec": { + "title": "child-folder" + }, +} +``` + +JSON Body schema: + +- **metadata.name** – The Grafana [unique identifier]({{< ref "#identifier-id-vs-unique-identifier-uid" >}}). If you do not want to provide this, set metadata.generateName to the prefix you would like for the uid. +- **metadata.annotations.grafana.app/folder** - Optional field, the unique identifier of the parent folder under which the folder should be created. Requires nested folders to be enabled. +- **spec.title** – The title of the folder. + +**Example Response**: + +```http +HTTP/1.1 200 +Content-Type: application/json +{ + "kind": "Folder", + "apiVersion": "folder.grafana.app/v1beta1", + "metadata": { + "name": "eef33r1fprd34d", + "namespace": "default", + "uid": "X8momvVZnsXdOqvLD9I4ngqLVif2CgRWXHy9xb2UgjQX", + "resourceVersion": "1741320415009", + "creationTimestamp": "2025-03-07T04:06:55Z", + "labels": { + "grafana.app/deprecatedInternalID": "1159" + }, + "annotations": { + "grafana.app/folder": "fef30w4jaxla8b", + "grafana.app/createdBy": "service-account:cef2t2rfm73lsb" + } + }, + "spec": { + "title": "child-folder" + } +} +``` + +Status Codes: + +- **201** – Created +- **400** – Errors (invalid json, missing or invalid fields, etc) +- **401** – Unauthorized +- **403** – Access denied +- **409** – Conflict (folder with the same uid already exists) + +### Update folder + +`PUT /apis/folder.grafana.app/v1beta1/namespaces/:namespace/folders/:uid` + +Updates an existing folder identified by uid. + +- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}). +- uid: the unique identifier of the folder to update. this will be the _name_ in the folder response + +**Required permissions** + +See note in the [introduction]({{< ref "#folder-api" >}}) for an explanation. + +| Action | Scope | +| --------------- | ----------- | +| `folders:write` | `folders:*` | + +**Example Request**: + +```http +PUT /apis/folder.grafana.app/v1beta1/namespaces/default/folders/fef30w4jaxla8b HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +"metadata": { + "name": "aef30vrzxs3y8d", + "annotations": { + "grafana.app/folder": "xkj92m5pqw3vn4" + } + } + "spec": { + "title": "updated title" + }, +``` + +JSON Body schema: + +- **metadata.name** – The [unique identifier]({{< ref "#identifier-id-vs-unique-identifier-uid" >}}) of the folder. +- **metadata.annotations.grafana.app/folder** - Optional field, the unique identifier of the parent folder under which the folder should be - update this to move the folder under a different parent folder. Requires nested folders to be enabled. +- **spec.title** – The title of the folder. + +**Example Response**: + +```http +HTTP/1.1 200 +Content-Type: application/json + +{ + "kind": "Folder", + "apiVersion": "folder.grafana.app/v1beta1", + "metadata": { + "name": "fef30w4jaxla8b", + "namespace": "default", + "uid": "YaWLsFrMwEaTlIQwX2iMnhHlJuZHtZugps50BQoyjXEX", + "resourceVersion": "1741345736000", + "creationTimestamp": "2025-03-07T11:08:56Z", + "annotations": { + "grafana.app/folder": "xkj92m5pqw3vn4", + "grafana.app/createdBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedTimestamp": "2025-03-07T11:08:56Z" + } + }, + "spec": { + "title": "updated title" + } +} +``` + +Status Codes: + +- **200** – Updated +- **400** – Errors (invalid json, missing or invalid fields, etc) +- **401** – Unauthorized +- **403** – Access Denied +- **404** – Folder not found +- **412** – Precondition failed (the folder has been changed by someone else). With this status code, the response body will have the following properties: + +```http +HTTP/1.1 412 Precondition Failed +Content-Type: application/json; charset=UTF-8 +Content-Length: 97 + +{ + "message": "The folder has been changed by someone else", + "status": "version-mismatch" +} +``` + +### Delete folder + +`DELETE /apis/folder.grafana.app/v1beta1/namespaces/:namespace/folders/:uid` + +Deletes an existing folder identified by UID along with all dashboards (and their alerts) stored in the folder. This operation cannot be reverted. + +If [Grafana Alerting]({{< relref "/docs/grafana/latest/alerting" >}}) is enabled, you can set an optional query parameter `forceDeleteRules=false` so that requests will fail with 400 (Bad Request) error if the folder contains any Grafana alerts. However, if this parameter is set to `true` then it will delete any Grafana alerts under this folder. + +- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}). +- uid: the unique identifier of the folder to delete. this will be the _name_ in the folder response + +**Required permissions** + +See note in the [introduction]({{< ref "#folder-api" >}}) for an explanation. + +| Action | Scope | +| ---------------- | ----------- | +| `folders:delete` | `folders:*` | + +**Example Request**: + +```http +DELETE /apis/folder.grafana.app/v1beta1/namespaces/default/folders/fef30w4jaxla8b HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +``` + +**Example Response**: + +```http +HTTP/1.1 200 +Content-Type: application/json + +{ + "kind": "Folder", + "apiVersion": "folder.grafana.app/v1beta1", + "metadata": { + "name": "fef30w4jaxla8b", + "namespace": "default", + "uid": "YaWLsFrMwEaTlIQwX2iMnhHlJuZHtZugps50BQoyjXEX", + "resourceVersion": "1741345736000", + "creationTimestamp": "2025-03-07T11:08:56Z", + "annotations": { + "grafana.app/folder": "xkj92m5pqw3vn4", + "grafana.app/createdBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedTimestamp": "2025-03-07T11:08:56Z" + } + }, + "spec": { + "title": "updated title" + } +} +``` + +Status Codes: + +- **200** – Deleted +- **401** – Unauthorized +- **400** – Bad Request +- **403** – Access Denied +- **404** – Folder not found + +## APIs + ## Identifier (id) vs unique identifier (uid) -The identifier (id) of a folder is an auto-incrementing numeric value and is only unique per Grafana install. - -The unique identifier (uid) of a folder can be used for uniquely identify folders between multiple Grafana installs. It's automatically generated if not provided when creating a folder. The uid allows having consistent URLs for accessing folders and when syncing folders between multiple Grafana installs. This means that changing the title of a folder will not break any bookmarked links to that folder. +The unique identifier (uid) of a folder can be used for uniquely identify folders within an org. It's automatically generated if not provided when creating a folder. The uid allows having consistent URLs for accessing folders and when syncing folders between multiple Grafana installs. This means that changing the title of a folder will not break any bookmarked links to that folder. The uid can have a maximum length of 40 characters. -## Get all folders +The identifier (id) of a folder is deprecated in favor of the unique identifier (uid). + +### Get all folders `GET /api/folders` @@ -75,7 +444,7 @@ Content-Type: application/json ] ``` -## Get folder by uid +### Get folder by uid `GET /api/folders/:uid` @@ -133,7 +502,7 @@ Status Codes: - **403** – Access Denied - **404** – Folder not found -## Create folder +### Create folder `POST /api/folders` @@ -207,7 +576,7 @@ Status Codes: - **403** – Access Denied - **412** - Folder already exists -## Update folder +### Update folder `PUT /api/folders/:uid` @@ -296,7 +665,7 @@ Content-Length: 97 } ``` -## Delete folder +### Delete folder `DELETE /api/folders/:uid` @@ -342,7 +711,7 @@ Status Codes: - **403** – Access Denied - **404** – Folder not found -## Move folder +### Move folder `POST /api/folders/:uid/move` diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index ad403d30d28..ff082322cb8 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -855,6 +855,23 @@ Path to the default home dashboard. If this value is empty, then Grafana uses St On Linux, Grafana uses `/usr/share/grafana/public/dashboards/home.json` as the default home dashboard location. {{< /admonition >}} +### `[dashboard_cleanup]` + +Settings related to cleaning up associated dashboards information if the dashboard was deleted through /apis. + +#### `interval` + +How often to run the job to cleanup associated resources. The default interval is `30s`. The minimum allowed value is `10s` to ensure the system isn't overloaded. + +The interval string must include a unit suffix (ms, s, m, h), e.g. 30s or 1m. + +#### `batch_size` + +Number of deleted dashboards to process in each batch during the cleanup process. +Default: `10`, Minimum: `5`, Maximum: `200`. + +Increasing this value allows processing more dashboards in each cleanup cycle but may impact system performance. +
### `[datasources]` From 43d0053c806e4bbca926290293f176d741a16518 Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Fri, 25 Apr 2025 05:06:49 -0600 Subject: [PATCH 116/146] K8s: Dashboards: Fix transformation between v1 and v2 (#104502) --- apps/dashboard/kinds/v2alpha1/dashboard_spec.cue | 2 +- .../pkg/apis/dashboard/v2alpha1/dashboard_spec.cue | 2 +- .../pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go | 2 +- .../apis/dashboard/v2alpha1/zz_generated.openapi.go | 2 +- .../zz_generated.openapi_violation_exceptions.list | 1 + .../dashboard.grafana.app-v2alpha1.json | 10 +++++----- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue b/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue index 31bfcc723cf..f2da39df16c 100644 --- a/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue +++ b/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue @@ -78,7 +78,7 @@ AnnotationPanelFilter: { exclude?: bool | *false // Panel IDs that should be included or excluded - ids: [...uint8] + ids: [...uint32] } // "Off" for no shared crosshair or tooltip (default). diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec.cue b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec.cue index a369449ea0e..85641129095 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec.cue +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec.cue @@ -82,7 +82,7 @@ AnnotationPanelFilter: { exclude?: bool | *false // Panel IDs that should be included or excluded - ids: [...uint8] + ids: [...uint32] } // "Off" for no shared crosshair or tooltip (default). diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go index 23d3c77fa76..877c2acc44e 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go @@ -73,7 +73,7 @@ type DashboardAnnotationPanelFilter struct { // Should the specified panels be included or excluded Exclude *bool `json:"exclude,omitempty"` // Panel IDs that should be included or excluded - Ids []uint8 `json:"ids"` + Ids []uint32 `json:"ids"` } // NewDashboardAnnotationPanelFilter creates a new DashboardAnnotationPanelFilter object. diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go index f777f9c4f82..8df2424f977 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go @@ -544,7 +544,7 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardAnnotationPanelFilter(ref commo SchemaProps: spec.SchemaProps{ Default: 0, Type: []string{"integer"}, - Format: "byte", + Format: "int64", }, }, }, diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi_violation_exceptions.list b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi_violation_exceptions.list index 17477f9db13..889a4fcdfed 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi_violation_exceptions.list +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi_violation_exceptions.list @@ -3,6 +3,7 @@ API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/ API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardAdhocVariableSpec,BaseFilters API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardAdhocVariableSpec,DefaultKeys API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardAdhocVariableSpec,Filters +API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardAnnotationPanelFilter,Ids API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardAutoGridLayoutSpec,Items API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardConditionalRenderingGroupSpec,Items API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardCustomVariableSpec,Options diff --git a/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v2alpha1.json b/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v2alpha1.json index 77707413bb4..296e6a4e6bf 100644 --- a/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v2alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v2alpha1.json @@ -1087,6 +1087,9 @@ "type": "string", "default": "" }, + "origin": { + "type": "string" + }, "value": { "type": "string", "default": "" @@ -1104,9 +1107,6 @@ "type": "string", "default": "" } - }, - "origin": { - "type": "string" } } }, @@ -1215,7 +1215,7 @@ "type": "array", "items": { "type": "integer", - "format": "byte", + "format": "int64", "default": 0 } } @@ -4588,4 +4588,4 @@ } } } -} +} \ No newline at end of file From ebf3a8d205ebd3633dd447d2bc66ec50eaaf9200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20Sencer=20=C3=96zcan?= <32759850+mustafasencer@users.noreply.github.com> Date: Fri, 25 Apr 2025 13:43:35 +0200 Subject: [PATCH 117/146] fix: make data syncer run configurable on mode (2->3) change (#104401) --- go.mod | 2 +- pkg/apiserver/rest/dualwriter.go | 4 ++++ pkg/apiserver/rest/dualwriter_syncer.go | 1 + pkg/apiserver/rest/dualwriter_test.go | 9 +++++++++ pkg/services/apiserver/builder/helper.go | 3 +++ pkg/setting/setting.go | 1 + pkg/setting/setting_unified_storage.go | 4 ++++ 7 files changed, 23 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 054f66e485a..68709329747 100644 --- a/go.mod +++ b/go.mod @@ -455,7 +455,7 @@ require ( github.com/mithrandie/ternary v1.1.1 // indirect github.com/moby/spdystream v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/mschoch/smat v0.2.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect diff --git a/pkg/apiserver/rest/dualwriter.go b/pkg/apiserver/rest/dualwriter.go index 6cd37cb8740..3b4196935de 100644 --- a/pkg/apiserver/rest/dualwriter.go +++ b/pkg/apiserver/rest/dualwriter.go @@ -164,6 +164,10 @@ func SetDualWritingMode( return Mode0, errDualWriterSetCurrentMode } case cfg.Mode >= Mode3 && currentMode < Mode3: + if cfg.SkipDataSync { + return currentMode, nil + } + // Transitioning to Mode3 or higher requires data synchronization. cfgModeTmp := cfg.Mode // Before running the sync, set the syncer config to the current mode, as we have to run the syncer diff --git a/pkg/apiserver/rest/dualwriter_syncer.go b/pkg/apiserver/rest/dualwriter_syncer.go index b15cf5376ac..fb064370fc6 100644 --- a/pkg/apiserver/rest/dualwriter_syncer.go +++ b/pkg/apiserver/rest/dualwriter_syncer.go @@ -36,6 +36,7 @@ type SyncerConfig struct { LegacyStorage Storage Storage Storage ServerLockService ServerLockService + SkipDataSync bool DataSyncerInterval time.Duration DataSyncerRecordsLimit int diff --git a/pkg/apiserver/rest/dualwriter_test.go b/pkg/apiserver/rest/dualwriter_test.go index 3faf131424e..fa6fa7e5f4a 100644 --- a/pkg/apiserver/rest/dualwriter_test.go +++ b/pkg/apiserver/rest/dualwriter_test.go @@ -20,6 +20,7 @@ func TestSetDualWritingMode(t *testing.T) { kvStore *fakeNamespacedKV desiredMode DualWriterMode expectedMode DualWriterMode + skipDataSync bool serverLockError error } tests := @@ -61,6 +62,13 @@ func TestSetDualWritingMode(t *testing.T) { expectedMode: Mode2, serverLockError: fmt.Errorf("lock already exists"), }, + { + name: "should keep mode2 when trying to go from mode2 to mode3 and migration is disabled", + kvStore: &fakeNamespacedKV{data: map[string]string{"playlist.grafana.app/playlists": "2"}, namespace: "storage.dualwriting"}, + desiredMode: Mode3, + expectedMode: Mode2, + skipDataSync: true, + }, } for _, tt := range tests { @@ -86,6 +94,7 @@ func TestSetDualWritingMode(t *testing.T) { Storage: us, Kind: "playlist.grafana.app/playlists", Mode: tt.desiredMode, + SkipDataSync: tt.skipDataSync, ServerLockService: serverLockSvc, RequestInfo: &request.RequestInfo{}, Reg: p, diff --git a/pkg/services/apiserver/builder/helper.go b/pkg/services/apiserver/builder/helper.go index 49d399e3914..bfd9040e9c2 100644 --- a/pkg/services/apiserver/builder/helper.go +++ b/pkg/services/apiserver/builder/helper.go @@ -315,6 +315,7 @@ func InstallAPIs( var ( dualWriterPeriodicDataSyncJobEnabled bool + dualWriterMigrationDataSyncDisabled bool dataSyncerInterval = time.Hour dataSyncerRecordsLimit = 1000 ) @@ -323,6 +324,7 @@ func InstallAPIs( if resourceExists { mode = resourceConfig.DualWriterMode dualWriterPeriodicDataSyncJobEnabled = resourceConfig.DualWriterPeriodicDataSyncJobEnabled + dualWriterMigrationDataSyncDisabled = resourceConfig.DualWriterMigrationDataSyncDisabled dataSyncerInterval = resourceConfig.DataSyncerInterval dataSyncerRecordsLimit = resourceConfig.DataSyncerRecordsLimit } @@ -343,6 +345,7 @@ func InstallAPIs( Kind: key, RequestInfo: requestInfo, Mode: mode, + SkipDataSync: dualWriterMigrationDataSyncDisabled, LegacyStorage: legacy, Storage: storage, ServerLockService: serverLock, diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 4a77bfdbf04..f0cd2815fd6 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -556,6 +556,7 @@ type Cfg struct { type UnifiedStorageConfig struct { DualWriterMode rest.DualWriterMode DualWriterPeriodicDataSyncJobEnabled bool + DualWriterMigrationDataSyncDisabled bool // DataSyncerInterval defines how often the data syncer should run for a resource on the grafana instance. DataSyncerInterval time.Duration // DataSyncerRecordsLimit defines how many records will be processed at max during a sync invocation. diff --git a/pkg/setting/setting_unified_storage.go b/pkg/setting/setting_unified_storage.go index 5bc9fcf2ec1..c805f7a330f 100644 --- a/pkg/setting/setting_unified_storage.go +++ b/pkg/setting/setting_unified_storage.go @@ -30,6 +30,9 @@ func (cfg *Cfg) setUnifiedStorageConfig() { // parse dualWriter periodic data syncer config dualWriterPeriodicDataSyncJobEnabled := section.Key("dualWriterPeriodicDataSyncJobEnabled").MustBool(false) + // parse dualWriter migration data sync disabled from resource section + dualWriterMigrationDataSyncDisabled := section.Key("dualWriterMigrationDataSyncDisabled").MustBool(false) + // parse dataSyncerRecordsLimit from resource section dataSyncerRecordsLimit := section.Key("dataSyncerRecordsLimit").MustInt(1000) @@ -39,6 +42,7 @@ func (cfg *Cfg) setUnifiedStorageConfig() { storageConfig[resourceName] = UnifiedStorageConfig{ DualWriterMode: rest.DualWriterMode(dualWriterMode), DualWriterPeriodicDataSyncJobEnabled: dualWriterPeriodicDataSyncJobEnabled, + DualWriterMigrationDataSyncDisabled: dualWriterMigrationDataSyncDisabled, DataSyncerRecordsLimit: dataSyncerRecordsLimit, DataSyncerInterval: dataSyncerInterval, } From b5281c923f36039adafcb153eca61fd8f851c84f Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Fri, 25 Apr 2025 05:55:50 -0600 Subject: [PATCH 118/146] K8s: Dashboards: Fix provisioned dashboard cleanup (#104504) --- .../apis/dashboard/legacy/sql_dashboards.go | 22 ++++++++----------- .../dashboards/service/dashboard_service.go | 5 ++++- .../service/dashboard_service_test.go | 2 ++ 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go index b7a660f53e6..3c032613b05 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go @@ -303,19 +303,15 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows, history bool) (*dashboardRo } if origin_name.String != "" { - // if the reader cannot be found, it may be an orphaned provisioned dashboard - resolvedPath := a.provisioning.GetDashboardProvisionerResolvedPath(origin_name.String) - if resolvedPath != "" { - meta.SetSourceProperties(utils.SourceProperties{ - Path: origin_path.String, - Checksum: origin_hash.String, - TimestampMillis: origin_ts.Int64, - }) - meta.SetManagerProperties(utils.ManagerProperties{ - Kind: utils.ManagerKindClassicFP, // nolint:staticcheck - Identity: origin_name.String, - }) - } + meta.SetSourceProperties(utils.SourceProperties{ + Path: origin_path.String, + Checksum: origin_hash.String, + TimestampMillis: origin_ts.Int64, + }) + meta.SetManagerProperties(utils.ManagerProperties{ + Kind: utils.ManagerKindClassicFP, // nolint:staticcheck + Identity: origin_name.String, + }) } else if plugin_id.String != "" { meta.SetManagerProperties(utils.ManagerProperties{ Kind: utils.ManagerKindPlugin, diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index e9e3f695850..1206e1944cd 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -1161,8 +1161,11 @@ func (dr *DashboardServiceImpl) UnprovisionDashboard(ctx context.Context, dashbo UpdatedAt: time.Now(), Dashboard: dash.Data, }, nil, true) + if err != nil { + return err + } - return err + return dr.dashboardStore.UnprovisionDashboard(ctx, dashboardId) } return dashboards.ErrDashboardNotFound diff --git a/pkg/services/dashboards/service/dashboard_service_test.go b/pkg/services/dashboards/service/dashboard_service_test.go index 15179f52a2e..a6607d36800 100644 --- a/pkg/services/dashboards/service/dashboard_service_test.go +++ b/pkg/services/dashboards/service/dashboard_service_test.go @@ -1119,6 +1119,7 @@ func TestUnprovisionDashboard(t *testing.T) { }, "spec": map[string]any{}, }} + fakeStore.On("UnprovisionDashboard", mock.Anything, int64(1)).Return(nil).Once() k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(dash, nil) dashWithoutAnnotations := &unstructured.Unstructured{Object: map[string]any{ "apiVersion": dashboardv0.APIVERSION, @@ -1169,6 +1170,7 @@ func TestUnprovisionDashboard(t *testing.T) { err := service.UnprovisionDashboard(ctx, 1) require.NoError(t, err) k8sCliMock.AssertExpectations(t) + fakeStore.AssertExpectations(t) }) } From b145deb5a053cd776e4766cd2e18848c98a216eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Irene=20Rodr=C3=ADguez?= Date: Fri, 25 Apr 2025 13:58:07 +0200 Subject: [PATCH 119/146] Add new grafanactl content (#104448) --- .../grafana-cli/_index.md | 52 ++++ .../grafana-cli/grafanacli-workflows.md | 226 ++++++++++++++++++ .../grafana-cli/install-grafana-cli.md | 51 ++++ .../grafana-cli/set-up-grafana-cli.md | 122 ++++++++++ 4 files changed, 451 insertions(+) create mode 100644 docs/sources/observability-as-code/grafana-cli/_index.md create mode 100644 docs/sources/observability-as-code/grafana-cli/grafanacli-workflows.md create mode 100644 docs/sources/observability-as-code/grafana-cli/install-grafana-cli.md create mode 100644 docs/sources/observability-as-code/grafana-cli/set-up-grafana-cli.md diff --git a/docs/sources/observability-as-code/grafana-cli/_index.md b/docs/sources/observability-as-code/grafana-cli/_index.md new file mode 100644 index 00000000000..a62d94aecbc --- /dev/null +++ b/docs/sources/observability-as-code/grafana-cli/_index.md @@ -0,0 +1,52 @@ +--- +_build: + list: false +noindex: true +cascade: + noindex: true +description: Overview of Grafana CLI, a command line tool for managing Grafana resources as code. +keywords: + - observability + - configuration + - as code + - as-code + - dashboards + - git integration + - git sync + - github +labels: + products: + - cloud + - enterprise + - oss +cards: + items: + - description: Learn how to install Grafana CLI + height: 24 + href: ./install-grafana-cli/ + title: Install Grafana CLI + - description: Set up Grafana CLI + height: 24 + href: ./set-up-grafana-cli/ + title: Set up your Grafana CLI + - description: Learn how to manage resources with Grafana CLI + height: 24 + href: ./grafanacli-workflows + title: Manage resources with Grafana CLI + title_class: pt-0 lh-1 +hero: + description: Grafana CLI (grafanactl) is a command-line tool designed to simplify interaction with Grafana instances. It enables users to authenticate, manage multiple environments, and perform administrative tasks through Grafana’s REST API, all from the terminal. Whether you're automating workflows in CI/CD pipelines or switching between staging and production environments, Grafana CLI provides a flexible and scriptable way to manage your Grafana setup efficiently. + height: 110 + level: 1 + title: Grafana CLI + width: 110 +title: Introduction to Grafana CLI +menuTitle: Grafana CLI +weight: 130 +--- + +{{< docs/hero-simple key="hero" >}} + +## Explore + +{{< card-grid key="cards" type="simple" >}} diff --git a/docs/sources/observability-as-code/grafana-cli/grafanacli-workflows.md b/docs/sources/observability-as-code/grafana-cli/grafanacli-workflows.md new file mode 100644 index 00000000000..980c6a92b83 --- /dev/null +++ b/docs/sources/observability-as-code/grafana-cli/grafanacli-workflows.md @@ -0,0 +1,226 @@ +--- +_build: + list: false +noindex: true +cascade: + noindex: true +description: Learn more about the supported workflows and use cases for Grafana CLI +keywords: + - workflows + - Grafana CLI + - CLI + - command line + - grafanactl +labels: + products: + - cloud + - enterprise + - oss +title: Manage resources with Grafana CLI +weight: 300 +--- + +# Manage resources with Grafana CLI + +{{< admonition type="note" >}} +`grafanactl` is under active development. Command-line flags and subcommands described here may change. This document outlines the target workflows the tool is expected to support. +{{< /admonition >}} + +## Migrate resources between environments + +Using the `config` and `resources` options, you can migrate Grafana resources from one environment to another, for example, from a development to production environment. +The `config` option lets you define the configuration context. +Using `resources` with `pull`, `push`, and `serve` lets you pull a defined resource from one instance, and push that resource to another instance. `Serve` allows you to preview changes locally before pushing. + +Use these steps to migrate resources between environments: + +{{< admonition type="note" >}} +Currently, the `serve` command only works with dashboards. +{{< /admonition >}} + +Use these steps to migrate resources between environments: + +{{< admonition type="note" >}} +Resources are pulled and pushed from the `./resources` directory by default. +This directory can be configured with the `--directory`/`-d` flags. +{{< /admonition >}} + +1. Make changes to dashboards and other resources using the Grafana UI in your **development instance**. +1. Pull those resources from the development environment to your local machine: + + ```bash + grafanactl config use-context YOUR_CONTEXT # for example "dev" + grafanactl resources pull -d ./resources/ -o yaml # or json + ``` + +1. (Optional) Preview the resources locally before pushing: + + ```bash + grafanactl config use-context YOUR_CONTEXT # for example "prod" + grafanactl resources serve -d ./resources/ + ``` + +1. Switch to the **production instance** and push the resources: + + ```bash + grafanactl config use-context YOUR_CONTEXT # for example "prod" + grafanactl resources push -d ./resources/ + ``` + +## Back up Grafana resources + +This workflow helps you back up all Grafana resources from one instance and later restore them. This is useful to replicate a configuration or perform disaster recovery. + +1. Use `grafanactl` to pull all resources from your target environment: + + ```bash + grafanactl config use-context YOUR_CONTEXT # for example "prod" + grafanactl resources pull -d ./resources/ -o yaml # or json + ``` + +1. Save the exported resources to version control or cloud storage. + +## Restore Grafana resources + +1. (Optional) Preview the backup locally: + + ```bash + grafanactl config use-context YOUR_CONTEXT # for example "prod" + grafanactl resources serve -d ./resources/ + ``` + +1. To restore the resources later or restore them on another instance, push the saved resources: + + ```bash + grafanactl config use-context YOUR_CONTEXT # for example "prod" + grafanactl resources push -d ./resources/ + ``` + +## Manage dashboards as code + +With this workflow, you can define and manage dashboards as code, saving them to a version control system like Git. This is useful for teams that want to maintain a history of changes, collaborate on dashboard design, and ensure consistency across environments. + +1. Use a dashboard generation script (for example, with the [Foundation SDK](https://github.com/grafana/grafana-foundation-sdk)). You can find an example implementation in the Grafana as code [hands-on lab repository](https://github.com/grafana/dashboards-as-code-workshop/tree/main/part-one-golang). + +1. Serve and preview the output of the dashboard generator locally: + + ```bash + grafanactl config use-context YOUR_CONTEXT # for example "dev" + grafanactl resources serve --script 'go run scripts/generate-dashboard.go' --watch './scripts' + ``` + +1. When the output looks correct, generate dashboard manifest files: + + ```bash + go run scripts/generate-dashboard.go --generate-resource-manifests --output './resources' + ``` + +1. Push the generated resources to your Grafana instance: + + ```bash + grafanactl config use-context YOUR_CONTEXT # for example "dev" + grafanactl resources push -d ./resources/ + ``` + +## Explore and modify resources from the terminal + +This section describes how to use the Grafana CLI to interact with Grafana resources directly from your terminal. These commands allow you to browse, inspect, update, and delete resources without using the Grafana UI. This approach is useful for advanced users who want to manage resources more efficiently or integrate Grafana operations into automated workflows. + +### Find and delete dashboards using invalid data sources + +Use this workflow to identify dashboards that reference incorrect or outdated data sources, and remove them if necessary. + +1. Set the context to the appropriate environment: + + ```bash + grafanactl config use-context YOUR_CONTEXT # for example "prod" + ``` + +1. Find dashboards using specific data sources: + + ```bash + grafanactl resources get dashboards -ojson | jq '.items | map({ uid: .metadata.name, datasources: .spec.panels | map(.datasource.uid) })' + [ + { + "uid": "important-production-dashboard", + "datasources": [ + "mimir-prod" + ] + }, + { + "uid": "test-dashboard-from-dev", + "datasources": [ + "mimir-prod", + "mimir-dev" + ] + }, + { + "uid": "test-dashboard-from-stg", + "datasources": [ + "mimir-prod", + "mimir-stg", + "mimir-dev" + ] + } + ] + ``` + + This command lists dashboard UIDs along with the data source UIDs used in their panels. You can then identify the dashboards that are using invalid or unexpected data sources. + +1. Delete the identified dashboards directly: + + ```bash + grafanactl resources delete dashboards/test-dashboard-from-stg,test-dashboard-from-dev + ✔ 2 resources deleted, 0 errors + ``` + +### Find and deprecate dashboards using the old API version + +Use this workflow to locate dashboards using a deprecated API version and mark them accordingly. + +1. Set the context to the appropriate environment: + + ```bash + grafanactl config use-context YOUR_CONTEXT # for example "prod" + ``` + +1. List all available resources types and versions: + + ```bash + grafanactl resources list + ``` + + This command returns a list of resources, including their versions, types, and quantities: + + ```bash + GROUP VERSION KIND + folder.grafana.app v1 folder + dashboard.grafana.app v1 dashboard + dashboard.grafana.app v1 librarypanel + dashboard.grafana.app v2 dashboard + dashboard.grafana.app v2 librarypanel + playlist.grafana.app v1 playlist + ``` + +1. Find dashboards that are still using an old API version: + + ```bash + grafanactl resources get dashboards.v1.dashboard.grafana.app + ``` + + This command returns a table displaying the resource type, resource name, and associated namespace: + + ```bash + KIND NAME NAMESPACE + dashboards really-old-dashboard default + ``` + +1. Edit each of these dashboards to add a `deprecated` tag: + + ```bash + grafanactl resources edit dashboards.v1.dashboard.grafana.app/really-old-dashboard -p '{"spec":{"tags":["deprecated"]}}' + ``` + +{{< admonition type="tip" >}} +You can get help by using the `grafanactl --help` command. +{{< /admonition >}} diff --git a/docs/sources/observability-as-code/grafana-cli/install-grafana-cli.md b/docs/sources/observability-as-code/grafana-cli/install-grafana-cli.md new file mode 100644 index 00000000000..b184fbc6df2 --- /dev/null +++ b/docs/sources/observability-as-code/grafana-cli/install-grafana-cli.md @@ -0,0 +1,51 @@ +--- +_build: + list: false +noindex: true +cascade: + noindex: true +description: Installation guide for Grafana CLI, a command line tool for managing Grafana Observability as Code +keywords: + - configuration + - Grafana CLI + - CLI + - command line + - grafanactl + - installation +labels: + products: + - cloud + - enterprise + - oss +title: Install Grafana CLI +weight: 100 +--- + +# Install Grafana CLI + +You can install the project using one of the following supported methods: + +## 1. Download a pre-built binary + +Download the latest binary for your platform from the [Releases page](https://github.com/grafana/grafanactl/releases). + +Prebuilt binaries are available for a variety of operating systems and architectures. Visit the latest release page, and scroll down to the Assets section. + +To install the binary, follow the instructions below: + +1. Download the archive for the desired operating system and architecture +1. Extract the archive +1. Move the executable to the desired directory +1. Ensure this directory is included in the PATH environment variable +1. Verify that you have execute permission on the file + +## 2. Build from source + +To build `grafanactl` from source you must: + +- Have `git` installed +- Have `go` v1.24 (or greater) installed + +```bash +go install github.com/grafana/grafanactl@latest +``` diff --git a/docs/sources/observability-as-code/grafana-cli/set-up-grafana-cli.md b/docs/sources/observability-as-code/grafana-cli/set-up-grafana-cli.md new file mode 100644 index 00000000000..8da1a45257d --- /dev/null +++ b/docs/sources/observability-as-code/grafana-cli/set-up-grafana-cli.md @@ -0,0 +1,122 @@ +--- +_build: + list: false +noindex: true +cascade: + noindex: true +description: Configuration guide for Grafana CLI, a command line tool for managing Grafana resources as code. +keywords: + - configuration + - Grafana CLI + - CLI + - command line + - grafanactl +labels: + products: + - cloud + - enterprise + - oss +title: Set up Grafana CLI +weight: 200 +--- + +# Set up Grafana CLI + +You can configure Grafana CLI in two ways: using environment variables or through a configuration file. + +- **Environment variables** are ideal for CI environments and support a single context. +- **Configuration files** can manage multiple contexts, making it easier to switch between different Grafana instances. + +## Use environment variables + +Grafana CLI communicates with Grafana via its REST API, which requires authentication credentials. + +At a minimum, set the URL of your Grafana instance and the organization ID: + +```bash +GRAFANA_SERVER='http://localhost:3000' GRAFANA_ORG_ID='1' grafanactl config check +``` + +Depending on your authentication method, you may also need to set: + +- A [token](https://github.com/grafana/grafanactl/blob/main/docs/reference/environment-variables/index.md#grafana_token) for a [Grafana service account](https://grafana.com/docs/grafana/latest/administration/service-accounts/) (recommended) +- A [username](https://github.com/grafana/grafanactl/blob/main/docs/reference/environment-variables/index.md#grafana_user) and [password](https://github.com/grafana/grafanactl/blob/main/docs/reference/environment-variables/index.md#grafana_password) for basic authentication + +To persist your configuration, consider [creating a context](#defining-contexts). + +A full list of supported environment variables is available in the [reference documentation](https://github.com/grafana/grafanactl/blob/main/docs/reference/environment-variables/index.md#environment-variables-reference). + +## Define contexts + +Contexts allow you to easily switch between multiple Grafana instances. By default, the CLI uses a context named `default`. + +To configure the `default` context: + +```bash +grafanactl config set contexts.default.grafana.server http://localhost:3000 +grafanactl config set contexts.default.grafana.org-id 1 + +# Authenticate with a service account token +grafanactl config set contexts.default.grafana.token service-account-token + +# Or use basic authentication +grafanactl config set contexts.default.grafana.user admin +grafanactl config set contexts.default.grafana.password admin +``` + +You can define additional contexts in the same way: + +```bash +grafanactl config set contexts.staging.grafana.server https://staging.grafana.example +grafanactl config set contexts.staging.grafana.org-id 1 +``` + +{{< admonition type="note" >}} +In these examples, `default` and `staging` are the names of the contexts. +{{< /admonition >}} + +## Configuration file + +Grafana CLI stores its configuration in a YAML file. The CLI determines the configuration file location in the following order: + +1. If the `--config` flag is provided, the specified file is used. +2. If `$XDG_CONFIG_HOME` is set: + `$XDG_CONFIG_HOME/grafanactl/config.yaml` +3. If `$HOME` is set: + `$HOME/.config/grafanactl/config.yaml` +4. If `$XDG_CONFIG_DIRS` is set: + `$XDG_CONFIG_DIRS/grafanactl/config.yaml` + +{{< admonition type="note" >}} +Use `grafanactl config check` to display the configuration file currently in use. +{{< /admonition >}} + +## Useful commands + +Check the current configuration: + +```bash +grafanactl config check +``` + +{{< admonition type="note" >}} +This command is useful to troubleshoot your configuration. +{{< /admonition >}} + +List all available contexts: + +```bash +grafanactl config list-contexts +``` + +Switch to a specific context: + +```bash +grafanactl config use-context staging +``` + +View the full configuration: + +```bash +grafanactl config view +``` From 06343fcda9320cafeb226887312d1112dce6ac04 Mon Sep 17 00:00:00 2001 From: Misi Date: Fri, 25 Apr 2025 14:14:44 +0200 Subject: [PATCH 120/146] Advisor: Recover correctly when step.Run panics (#104521) * wip * Add test case for it --- apps/advisor/pkg/app/utils.go | 11 +++++++++- apps/advisor/pkg/app/utils_test.go | 35 ++++++++++++++++++++++++++---- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/apps/advisor/pkg/app/utils.go b/apps/advisor/pkg/app/utils.go index 82290b24020..2dd4caf3de8 100644 --- a/apps/advisor/pkg/app/utils.go +++ b/apps/advisor/pkg/app/utils.go @@ -158,7 +158,16 @@ func runStepsInParallel(ctx context.Context, spec *advisorv0alpha1.CheckSpec, st go func(step checks.Step, item any) { defer wg.Done() defer func() { <-limit }() - stepErr, err := step.Run(ctx, spec, item) + var stepErr *advisorv0alpha1.CheckReportFailure + var err error + func() { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("panic recovered in step %s: %v", step.ID(), r) + } + }() + stepErr, err = step.Run(ctx, spec, item) + }() mu.Lock() defer mu.Unlock() if err != nil { diff --git a/apps/advisor/pkg/app/utils_test.go b/apps/advisor/pkg/app/utils_test.go index 830afb5950e..5d8e4441a18 100644 --- a/apps/advisor/pkg/app/utils_test.go +++ b/apps/advisor/pkg/app/utils_test.go @@ -129,6 +129,28 @@ func TestProcessCheck_RunError(t *testing.T) { assert.Equal(t, "error", obj.GetAnnotations()[checks.StatusAnnotation]) } +func TestProcessCheck_RunRecoversFromPanic(t *testing.T) { + obj := &advisorv0alpha1.Check{} + obj.SetAnnotations(map[string]string{}) + meta, err := utils.MetaAccessor(obj) + if err != nil { + t.Fatal(err) + } + meta.SetCreatedBy("user:1") + client := &mockClient{} + ctx := context.TODO() + + check := &mockCheck{ + items: []any{"item"}, + runPanics: true, + } + + err = processCheck(ctx, client, obj, check) + assert.Error(t, err) + assert.Contains(t, err.Error(), "panic recovered in step") + assert.Equal(t, "error", obj.GetAnnotations()[checks.StatusAnnotation]) +} + func TestProcessCheckRetry_NoRetry(t *testing.T) { obj := &advisorv0alpha1.Check{} obj.SetAnnotations(map[string]string{}) @@ -212,8 +234,9 @@ func (m *mockClient) PatchInto(ctx context.Context, id resource.Identifier, req } type mockCheck struct { - err error - items []any + err error + items []any + runPanics bool } func (m *mockCheck) ID() string { @@ -230,15 +253,19 @@ func (m *mockCheck) Item(ctx context.Context, id string) (any, error) { func (m *mockCheck) Steps() []checks.Step { return []checks.Step{ - &mockStep{err: m.err}, + &mockStep{err: m.err, panics: m.runPanics}, } } type mockStep struct { - err error + err error + panics bool } func (m *mockStep) Run(ctx context.Context, obj *advisorv0alpha1.CheckSpec, items any) (*advisorv0alpha1.CheckReportFailure, error) { + if m.panics { + panic("panic") + } if m.err != nil { return nil, m.err } From 52b120cb676ebdcb745cb9468018d7b3b71ee292 Mon Sep 17 00:00:00 2001 From: Juan Cabanas Date: Fri, 25 Apr 2025 09:48:32 -0300 Subject: [PATCH 121/146] Grafana UI: Add `ref` to `DatePickerWithInput` (#104346) Co-authored-by: Ashley Harrison --- .../DatePickerWithInput.tsx | 132 +++++++++--------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.tsx b/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.tsx index c1f151beded..e13dd5d8840 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.tsx @@ -1,6 +1,6 @@ import { css } from '@emotion/css'; import { autoUpdate, flip, shift, useClick, useDismiss, useFloating, useInteractions } from '@floating-ui/react'; -import { ChangeEvent, useState } from 'react'; +import { ChangeEvent, forwardRef, useImperativeHandle, useState } from 'react'; import { GrafanaTheme2, dateTime } from '@grafana/data'; @@ -11,7 +11,7 @@ import { DatePicker } from '../DatePicker/DatePicker'; export const formatDate = (date: Date | string) => dateTime(date).format('L'); /** @public */ -export interface DatePickerWithInputProps extends Omit { +export interface DatePickerWithInputProps extends Omit { /** Value selected by the DatePicker */ value?: Date | string; /** The minimum date the value can be set to */ @@ -27,78 +27,78 @@ export interface DatePickerWithInputProps extends Omit { - const [open, setOpen] = useState(false); - const styles = useStyles2(getStyles); +export const DatePickerWithInput = forwardRef( + ({ value, minDate, maxDate, onChange, closeOnSelect, placeholder = 'Date', ...rest }, ref) => { + const [open, setOpen] = useState(false); + const styles = useStyles2(getStyles); - // the order of middleware is important! - // see https://floating-ui.com/docs/arrow#order - const middleware = [ - flip({ - // see https://floating-ui.com/docs/flip#combining-with-shift - crossAxis: false, - boundary: document.body, - }), - shift(), - ]; + // the order of middleware is important! + // see https://floating-ui.com/docs/arrow#order + const middleware = [ + flip({ + // see https://floating-ui.com/docs/flip#combining-with-shift + crossAxis: false, + boundary: document.body, + }), + shift(), + ]; - const { context, refs, floatingStyles } = useFloating({ - open, - placement: 'bottom-start', - onOpenChange: setOpen, - middleware, - whileElementsMounted: autoUpdate, - strategy: 'fixed', - }); + const { context, refs, floatingStyles } = useFloating({ + open, + placement: 'bottom-start', + onOpenChange: setOpen, + middleware, + whileElementsMounted: autoUpdate, + strategy: 'fixed', + }); - const click = useClick(context); - const dismiss = useDismiss(context); - const { getReferenceProps, getFloatingProps } = useInteractions([dismiss, click]); + const click = useClick(context); + const dismiss = useDismiss(context); + const { getReferenceProps, getFloatingProps } = useInteractions([dismiss, click]); - return ( -
- ) => { - // Allow resetting the date - if (ev.target.value === '') { - onChange(''); - } - }} - className={styles.input} - {...rest} - {...getReferenceProps()} - /> -
- { - onChange(ev); - if (closeOnSelect) { - setOpen(false); + useImperativeHandle(ref, () => refs.domReference.current, [ + refs.domReference, + ]); + + return ( +
+ ) => { + // Allow resetting the date + if (ev.target.value === '') { + onChange(''); } }} - onClose={() => setOpen(false)} + className={styles.input} + {...rest} + {...getReferenceProps()} /> +
+ { + onChange(ev); + if (closeOnSelect) { + setOpen(false); + } + }} + onClose={() => setOpen(false)} + /> +
-
- ); -}; + ); + } +); + +DatePickerWithInput.displayName = 'DatePickerWithInput'; const getStyles = (theme: GrafanaTheme2) => { return { From f5ff6e38a3db4eaffe2d210b5312a033aea56f23 Mon Sep 17 00:00:00 2001 From: Igor Suleymanov Date: Fri, 25 Apr 2025 16:09:59 +0300 Subject: [PATCH 122/146] Update grafanactl install instructions in docs (#104539) --- .../observability-as-code/grafana-cli/install-grafana-cli.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/observability-as-code/grafana-cli/install-grafana-cli.md b/docs/sources/observability-as-code/grafana-cli/install-grafana-cli.md index b184fbc6df2..3277090c21f 100644 --- a/docs/sources/observability-as-code/grafana-cli/install-grafana-cli.md +++ b/docs/sources/observability-as-code/grafana-cli/install-grafana-cli.md @@ -47,5 +47,5 @@ To build `grafanactl` from source you must: - Have `go` v1.24 (or greater) installed ```bash -go install github.com/grafana/grafanactl@latest +go install github.com/grafana/grafanactl/cmd@latest ``` From 6b44b38c10e850fab149d65815b0434e3d02f78f Mon Sep 17 00:00:00 2001 From: Victor Marin <36818606+mdvictor@users.noreply.github.com> Date: Fri, 25 Apr 2025 16:19:08 +0300 Subject: [PATCH 123/146] DashboardReload: Do not preserve or restore URL state if dashboard version invalid (#104375) do not preserve or restore url state if dashboard version invalid --- .../utils/dashboardSessionState.test.ts | 24 +++++++++++++++++++ .../utils/dashboardSessionState.ts | 6 ++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/utils/dashboardSessionState.test.ts b/public/app/features/dashboard-scene/utils/dashboardSessionState.test.ts index 80b1716e7e6..da3d6459dc0 100644 --- a/public/app/features/dashboard-scene/utils/dashboardSessionState.test.ts +++ b/public/app/features/dashboard-scene/utils/dashboardSessionState.test.ts @@ -29,6 +29,17 @@ describe('dashboardSessionState', () => { expect(window.sessionStorage.getItem(PRESERVED_SCENE_STATE_KEY)).toBeNull(); }); + it('should do nothing if dashboard version is 0', () => { + const scene = buildTestScene(); + scene.setState({ version: 0 }); + + const deactivate = scene.activate(); + expect(window.sessionStorage.getItem(PRESERVED_SCENE_STATE_KEY)).toBeNull(); + + deactivate(); + expect(window.sessionStorage.getItem(PRESERVED_SCENE_STATE_KEY)).toBeNull(); + }); + it('should capture dashboard scene state and save it to session storage on deactivation', () => { const scene = buildTestScene(); @@ -84,6 +95,19 @@ describe('dashboardSessionState', () => { expect(locationService.getLocation().search).toBe('?var-customVar=b&from=now-6h&to=now&timezone=browser'); }); + + it('should not restore state if dashboard version is 0', () => { + window.sessionStorage.setItem( + PRESERVED_SCENE_STATE_KEY, + '?var-customVarNotOnDB=b&from=now-5m&to=now&timezone=browser' + ); + const scene = buildTestScene(); + scene.setState({ version: 0 }); + + restoreDashboardStateFromLocalStorage(scene); + + expect(locationService.getLocation().search).toBe('?var-customVar=b&from=now-6h&to=now&timezone=browser'); + }); }); }); diff --git a/public/app/features/dashboard-scene/utils/dashboardSessionState.ts b/public/app/features/dashboard-scene/utils/dashboardSessionState.ts index 6ad764e2222..c31a29843ca 100644 --- a/public/app/features/dashboard-scene/utils/dashboardSessionState.ts +++ b/public/app/features/dashboard-scene/utils/dashboardSessionState.ts @@ -7,6 +7,10 @@ import { DashboardScene } from '../scene/DashboardScene'; export const PRESERVED_SCENE_STATE_KEY = `grafana.dashboard.preservedUrlFiltersState`; export function restoreDashboardStateFromLocalStorage(dashboard: DashboardScene) { + if (!dashboard.state.version) { + return; + } + const preservedUrlState = window.sessionStorage.getItem(PRESERVED_SCENE_STATE_KEY); if (preservedUrlState) { @@ -49,7 +53,7 @@ export function preserveDashboardSceneStateInLocalStorage(scene: DashboardScene) return () => { // Skipping saving state for default home dashboard - if (!scene.state.uid) { + if (!scene.state.uid || !scene.state.version) { return; } From 314e337d76df9adcecfd30f4a302fa9d73e56da9 Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Fri, 25 Apr 2025 14:22:57 +0100 Subject: [PATCH 124/146] Build swagger ui in seperate webpack build (#102046) * Build swagger ui in seperate webpack build * render grafana and swagger * include light theme * merge main * update webassets usage --------- Co-authored-by: Ryan McKinley --- .gitignore | 1 + Makefile | 1 + go.work.sum | 2 + package.json | 1 + pkg/api/dtos/index.go | 8 - pkg/api/frontendsettings.go | 2 +- pkg/api/index.go | 2 +- pkg/api/swagger.go | 2 +- .../testdata/sample-assets-manifest.json | 144 ++++-------------- pkg/api/webassets/webassets.go | 52 ++----- pkg/api/webassets/webassets_test.go | 110 +++---------- pkg/middleware/recovery.go | 2 +- pkg/services/frontend/index.go | 2 +- project.json | 11 ++ public/views/swagger.html | 7 +- scripts/webpack/webpack.common.js | 1 - scripts/webpack/webpack.swagger.js | 27 ++++ 17 files changed, 116 insertions(+), 259 deletions(-) create mode 100644 scripts/webpack/webpack.swagger.js diff --git a/.gitignore b/.gitignore index 5ddb7c03ed1..285c839f370 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ awsconfig /.awcache /dist /public/build +/public/build-swagger /emails/dist /reports /e2e/tmp diff --git a/Makefile b/Makefile index 60763468e11..7d7b366aa76 100644 --- a/Makefile +++ b/Makefile @@ -471,6 +471,7 @@ clean: ## Clean up intermediate build artifacts. @echo "cleaning" rm -rf node_modules rm -rf public/build + rm -rf public/build-swagger .PHONY: gen-ts gen-ts: diff --git a/go.work.sum b/go.work.sum index 60f369552b9..29ca90c46f7 100644 --- a/go.work.sum +++ b/go.work.sum @@ -826,6 +826,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk= github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM= github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo= @@ -1075,6 +1076,7 @@ github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qA github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/googleapis/cloud-bigtable-clients-test v0.0.2 h1:S+sCHWAiAc+urcEnvg5JYJUOdlQEm/SEzQ/c/IdAH5M= github.com/googleapis/cloud-bigtable-clients-test v0.0.2/go.mod h1:mk3CrkrouRgtnhID6UZQDK3DrFFa7cYCAJcEmNsHYrY= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= diff --git a/package.json b/package.json index 6206f07ec1f..857ac1ce10a 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "stats": "webpack --mode production --config scripts/webpack/webpack.prod.js --profile --json > compilation-stats.json", "storybook": "yarn workspace @grafana/ui storybook --ci", "storybook:build": "yarn workspace @grafana/ui storybook:build", + "swaggerui:build": "webpack --config scripts/webpack/webpack.swagger.js --progress", "themes-generate": "esbuild --target=es6 ./scripts/cli/generateSassVariableFiles.ts --bundle --platform=node --tsconfig=./scripts/cli/tsconfig.json | node", "themes:usage": "eslint . --ignore-pattern '*.test.ts*' --ignore-pattern '*.spec.ts*' --cache --plugin '@grafana' --rule '{ @grafana/theme-token-usage: \"error\" }'", "typecheck": "tsc --noEmit && yarn run packages:typecheck", diff --git a/pkg/api/dtos/index.go b/pkg/api/dtos/index.go index 2a55017e613..ad24aeb9033 100644 --- a/pkg/api/dtos/index.go +++ b/pkg/api/dtos/index.go @@ -42,8 +42,6 @@ type EntryPointAssets struct { CSSFiles []EntryPointAsset `json:"cssFiles"` Dark string `json:"dark"` Light string `json:"light"` - Swagger []EntryPointAsset `json:"swagger"` - SwaggerCSSFiles []EntryPointAsset `json:"swaggerCssFiles"` } type EntryPointAsset struct { @@ -64,10 +62,4 @@ func (a *EntryPointAssets) SetContentDeliveryURL(prefix string) { for i, p := range a.CSSFiles { a.CSSFiles[i].FilePath = prefix + p.FilePath } - for i, p := range a.Swagger { - a.Swagger[i].FilePath = prefix + p.FilePath - } - for i, p := range a.SwaggerCSSFiles { - a.SwaggerCSSFiles[i].FilePath = prefix + p.FilePath - } } diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 5333033d2bc..6a001ea44a2 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -73,7 +73,7 @@ func (hs *HTTPServer) GetFrontendAssets(c *contextmodel.ReqContext) { // Assets hash.Reset() - dto, err := webassets.GetWebAssets(c.Req.Context(), hs.Cfg, hs.License) + dto, err := webassets.GetWebAssets(c.Req.Context(), "build", hs.Cfg, hs.License) if err == nil && dto != nil { _, _ = hash.Write([]byte(dto.ContentDeliveryURL)) _, _ = hash.Write([]byte(dto.Dark)) diff --git a/pkg/api/index.go b/pkg/api/index.go index 016a78366c6..fe2e426e3bd 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -114,7 +114,7 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV } theme := hs.getThemeForIndexData(prefs.Theme, urlPrefs.Theme) - assets, err := webassets.GetWebAssets(c.Req.Context(), hs.Cfg, hs.License) + assets, err := webassets.GetWebAssets(c.Req.Context(), "build", hs.Cfg, hs.License) if err != nil { return nil, err } diff --git a/pkg/api/swagger.go b/pkg/api/swagger.go index 6e34efdf82a..c35b08645bd 100644 --- a/pkg/api/swagger.go +++ b/pkg/api/swagger.go @@ -23,7 +23,7 @@ func (hs *HTTPServer) registerSwaggerUI(r routing.RouteRegister) { // The swagger based api navigator r.Get("/swagger", func(c *contextmodel.ReqContext) { ctx := c.Req.Context() - assets, err := webassets.GetWebAssets(ctx, hs.Cfg, hs.License) + assets, err := webassets.GetWebAssets(ctx, "build-swagger", hs.Cfg, hs.License) if err != nil { errhttp.Write(ctx, err, c.Resp) return diff --git a/pkg/api/webassets/testdata/sample-assets-manifest.json b/pkg/api/webassets/testdata/sample-assets-manifest.json index 7e6791143a0..03ba91f1aeb 100644 --- a/pkg/api/webassets/testdata/sample-assets-manifest.json +++ b/pkg/api/webassets/testdata/sample-assets-manifest.json @@ -1,135 +1,49 @@ { - "AdminAuthentication.js": { - "src": "public/build/AdminAuthentication.abcc504db867d1fa3469.js", - "integrity": "sha256-GtJEBbWfWAU2h2hoW+OiHUHNhHuKDzJ6mBEsVKyRc6M= sha384-SfBCROVbdjMZbxWZzYcYq1Ur4qVOmMgCQwRj9rc23op01045mOoRnCSwnENSNnhY sha512-RwKiHjN7M4Wf5BtO0nKFMoZTp0mCPVttkg4SU76+93VVdf8XEnTIpM6Trd+v4GX37wNrWEd34lh/Xaidq5t33g==" + "1085.e73a523ceed532bcdab0.js": { + "src": "public/build/runtime.d0d77f9ceac402028085.js", + "integrity": "sha384-AAAA" }, - "AdminEditOrgPage.js": { - "src": "public/build/AdminEditOrgPage.960dc1174d73f4fd46c3.js", - "integrity": "sha256-y9TYWD3xHk5MQiBpvSpKvFgtp7yHovE0hfrUppxsQuw= sha384-kCrsPv6DmRzbRW/ajBaGu5X67nNRRAuOaZZYONCm8YA6KQclrEHG8navPSsMROnk sha512-2oE8w7f0s/vo6SOgDteuYSvuvAAjqMFjPpD4t7b11sah4B007L9dIVs9MF3jTSKsSHlG61hK6L2QCgv+9FtrFg==" + "1085.0549a3fcb50e73c4b256.js": { + "src": "public/build/6029.0549a3fcb50e73c4b256.js", + "integrity": "sha384-BBBB" }, - "AdminFeatureTogglesPage.js": { - "src": "public/build/AdminFeatureTogglesPage.999db7797f1efddde074.js", - "integrity": "sha256-fhZz2BcvirKLQlxqcEprDplsSRAWYu+FiZWMRtFig1g= sha384-V2e+1wmlsKjorbNlJNzDfizXd1E4q8VpqaIX8S8FkumdZwFPPCXMsnHd77rjFKks sha512-LSiPxyiP0tmZ7fS/QOQBnC98pqgLDSATU0V9He5ubNEgziolxd/XmqoSDTGtp1WWZfjDxW6SUoKVmwstc0VzxA==" - }, - "AdminListOrgsPage.js": { - "src": "public/build/AdminListOrgsPage.c573cf876050fc991990.js", - "integrity": "sha256-RSxk6cc52+j9cITdwi4k5lgzER+sKF7Mx5pn6atMYdE= sha384-xE53wdhi0O9e9A+9NKHGlvJgEeSew6ooXYl6oS8e5Qq9slP2TQygvO48/3CCYWJM sha512-YfXZHm9jyZbHAzcAimOv96ZJWEpQrzjZPxxvyVZZVPfB/8Z1FL7rhKhk0HGIhufXTTLhobZp/9CkFKT4no7zTA==" - }, - "AdminSettings.js": { - "src": "public/build/AdminSettings.cfdfc2e959a29968d661.js", - "integrity": "sha256-v/5L4MXpTnSZXjApozisGbt+y+DPfpjWhH3D6bY83zE= sha384-FTIo/3tLgPKqgxwpxXSa8SLoU5hz4HJcgrVgXD2g06/GUnBFLrJcN3lYqYrvsgLu sha512-kBBoMB2XBtXM/iWW0zAFdI9O3ggetQZdpj6LhsiPDLy9pwRhwPQlnwZjlFkJMMEX0YK2uYl3dn9DUUyTKjtVMg==" - }, - "AlertAmRoutes.js": { - "src": "public/build/AlertAmRoutes.bd900447d272cba91413.js", - "integrity": "sha256-K6hY1ruuidLNg5aYV4XZVBpTFCcS7T2q4b1mF9lPOf4= sha384-SFNQUdxgwLjHW/JsukI7Juhs6f2+qdxMCNbsD4ix3Ha44HbiXTKPBTmvxMl4r+CS sha512-eSrh2wrOXlLp9bXd+0zsZS4v8xjJTufSi0Kfgx0FSB8WhnjhOnIOX37wszsV00w1AwyO6VE6yN3q2PGTrpUoeg==" - }, - "AlertGroups.js": { - "src": "public/build/AlertGroups.d04aee7a0d92a3726e5c.js", - "integrity": "sha256-GrsAPOHbrj3qJx97ugYdqF7fEtD1KU+VGZC9EnCzXhM= sha384-WjJwpX218WLCpDgRSkYbHvwjFy0ttlIb9fYpG0pLDgRu9iYQKBZXaYMU9PDKB4Iu sha512-hpE6YahXnP4RSOJ3Xd+oS55gmtDWHDnMrDNr2UOXpWbLuQQJu8e8uRAW5cIBl8u2S63F1SC1vGx6d535hECq2A==" - }, - "AlertRuleListIndex.js": { - "src": "public/build/AlertRuleListIndex.4682f9ab8dd5a8e68722.js", - "integrity": "sha256-xlMu10aTkvXAw8hmtsaEZXTrAqQjaglNkwS6GLRISnU= sha384-hTb2gf6p1Q/M7fULhnDzGOlVjuYjnrVAJCrQ+yNNr0T0EWZkJ+yxrjNh9Q7/9F+G sha512-8xMEU7qfa+rlm62FHWYFb4JNN3E0WNqbhjVdNA2Awq+ZbhSHuzb4XPnmDJoOzakZoG1s+ZgK7GL1wSXBrKbPkw==" - }, - "AlertSilences.js": { - "src": "public/build/AlertSilences.84de2bedd3634a40f4a4.js", - "integrity": "sha256-LamykmTC703pkL5q7hPdOp9zTQWn6ZsMKzRv7v35ubU= sha384-VWDeB7KZFOYL0RWBBXcmvzKds+Yxn2cDU8fSo4Srg165GY29s8+jO6sEG6OY9zj9 sha512-UvlIwoREIwdwkRWdut8+p+GGRgWTyAijW3pNQ1lQRUE8wGNYqBuHa1lGK6Jq4mmhkp1Pqo4/iI4Uk7vrbYFKow==" - }, - "app.css": { - "src": "public/build/grafana.app.91aaa9d81398c147a57c.css", - "integrity": "sha256-77rfikk+dYkH82TOmcmleVoDOHZQdhzVX9gDLcgPbtQ= sha384-IOTlZ1IvTVq5ekKLoaE3/SoZ12K1eExOAnSw9BzkgQ3+RcyQpb1S5hO2w//IIkRB sha512-0Ct3uJBFQIkyxYTvMxseA1cphe2RivXQ2MCbiV0hEm5NzWPiY9sq2P4ay5dXz5v35c++4W47KaknoWlc83bQJQ==" - }, - "app.js": { - "src": "public/build/app.js", - "integrity": "sha256-IOZKp3piC3vddDXP5jy5rIw0vb0KKEOg/k9EGrIxskk= sha384-CBNr5W0pJ23LQMnz5BZI1iVBIExOmF/wpqkEnBtYu9R/yYJIzjpv8KT0a3TBulOi sha512-ockzlzgosuZvLittZrSzh8lexEIZF9iKpy6J9Ii4es3e4D34FpWhHJhDZGpxLVraX4ypLofrDp2Yy0sdUbdi7w==" - }, - "dark.css": { - "src": "public/build/grafana.dark.722d809dba5a31f57d49.css", - "integrity": "sha256-kyPBeKRFdG+i4aLA6vZ0nIkSD25YTHjcCfQ3e2+M6AA= sha384-o2yYT3suDP1EtvTj4UzLsSS+AAlv0F+n5YVaxDePRM6LSGAEPTxFOfxB3myAqD/x sha512-H3Eos9Ff7d+tB7e+6RORDXY49LjNMa8X3ZxiFn8T/OlHUFRnsfXsvj65p9cMqaRom/4qqx0JtmhtnuBoeaysrg==" - }, - "dark.js": { - "src": "public/build/dark.js", - "integrity": "sha256-ON8tNAimyYNoju+WIQP7ux1Aq/oYspSbqoOKvVkeEJY= sha384-figjklDEMn19kCLZ9aq9pUeMGVOC3nlFkQI0PT016cep5KvAd4nyrAl4WA1MALHX sha512-P1xsw+PNS2AVneN4UFfYhYiS3FeEIDPAj4VXAjiQtHqw7qLETqv2K0sYRogoXma23Q/b+h3VL+fw6qtwhJ8WCQ==" - }, - "default-packages_grafana-data_src_field_fieldComparers_ts-packages_grafana-data_src_transform-9dcf20.js": { - "src": "public/build/default-packages_grafana-data_src_field_fieldComparers_ts-packages_grafana-data_src_transform-9dcf20.js", - "integrity": "sha256-BfaH57oBHA9HC5GwpCNAREwyuEGsxbPR+eOEarSMqr0= sha384-elHEqjfd9Guir6jsvCIBsh/wCF4oJ6/wTFfgK9TsKyfyUJecmK3y1qlczBevMotL sha512-92Uk9x490/MViSclM7PjI6iQrM3FNRuiHGP5CWqNFjKX7PWLTcBirSs1xkoZ3tc0QG0m+klaJg6eNVEvpF25dQ==" - }, - "default-packages_grafana-schema_src_raw_composable_barchart_panelcfg_x_BarChartPanelCfg_types-d3da31.js": { - "src": "public/build/default-packages_grafana-schema_src_raw_composable_barchart_panelcfg_x_BarChartPanelCfg_types-d3da31.js", - "integrity": "sha256-elDlrzSExWWpznC49+xQWQletnWQ1Fl6VaarANz2oLk= sha384-2UdE8X6NbOjt/i0WXpvew+eSBXKSTySfFdfxrIrALT0erSyvdbB9oASWPfRuTZyi sha512-so/nTAcSfmcReoZrJUusUbj6xxtGdrKiJ50n0CMZ1yqBh2hB7Y15s4POeX8axeJTNlo59wJ5GAbsdVTy4c0QhA==" - }, - "default-packages_grafana-ui_src_components_Layout_Stack_Stack_tsx-packages_grafana-ui_src_com-2a3620.js": { - "src": "public/build/default-packages_grafana-ui_src_components_Layout_Stack_Stack_tsx-packages_grafana-ui_src_com-2a3620.js", - "integrity": "sha256-+0bPuBGKFGglkXvW4oPiolrNveozRLZVLUrbCYsbVcM= sha384-EIayAgykdDWmyilAuXo4ad96v3tRqdWZp+BHdeDpSSsbAMeg+eoBBbk2Yh219kDg sha512-+jn7kmQ9Id8aTIe66TD+vM+W19cTIVexEfkxbxgqXdJyJ72qalN6ccWyP1ro1w/E1R/laZGNLz1LBc1I4u2Isw==" - }, - "defaultVendors-node_modules_braintree_sanitize-url_dist_index_js-node_modules_emotion_css_dis-e9d917.js": { - "src": "public/build/defaultVendors-node_modules_braintree_sanitize-url_dist_index_js-node_modules_emotion_css_dis-e9d917.js", - "integrity": "sha256-6et8tLQscwAftNYIM4u8L42Xi+tFIlkPsKIlyATTuY4= sha384-9AKIelnSKqxpMktU6Fw1uE1Kz6ZuEtI2ExvQ1PTjwPmpAE41LS6WxQ3pSV6qlLyz sha512-zcqwJExa8qzqZ1cCIK/y+HrWhJxP6rgSC+SbqxRStQUZrU4S1DQa1pwe6eSoZ4DPht/8rSeIii1wnl3DyhdVVg==" - }, - "defaultVendors-node_modules_fingerprintjs_fingerprintjs_dist_fp_esm_js-node_modules_grafana_e-45bb7f.js": { - "src": "public/build/defaultVendors-node_modules_fingerprintjs_fingerprintjs_dist_fp_esm_js-node_modules_grafana_e-45bb7f.js", - "integrity": "sha256-bVtkMWml/TQfwbkV9ueteGi+GhLh7ajxtVevi3gb4Aw= sha384-8+VsDObIkyeW90JyQELbiOsPKFJaBOmScMhBuWKmhtBmV9/7YvjIvqEPvpz1H5gP sha512-L3PzrsTEqfR+ruNzEaIqt0V0ZyX4V1fqbOe7aXWMIOr8JjJSJT1rMPVqJixy5DOL2f0zLDEc/DJvk4SN4BrDuw==" - }, - "defaultVendors-node_modules_swagger-ui-react_index_mjs.js": { - "src": "public/build/defaultVendors-node_modules_swagger-ui-react_index_mjs.js", - "integrity": "sha256-7qtYNxawpAwQUd6KR+2IgH95hkr0w5hZLGFeubAUkcc= sha384-29fQncD6F/vaoKmk4Ccht9JbnrPo2BSyUdHSkTkF4RWEFkhgVEUo1TlSTDc3iOcd sha512-UhXtPd5nXyvyVR0vNYoO5wINJgb49oeYoqfnNaVQndg3n9uIvPi4lr3iUk/jkBsnVH1dEDWiZIErDPiTyHe4+w==" - }, - "light.css": { - "src": "public/build/grafana.light.2fbd901d840329c18394.css", - "integrity": "sha256-OumSnRJ7qttDoFmbB3LMxVvrfpFFjABX5b1iZDcRMmk= sha384-Cq4wA1zPU1cqxA/pc2V+iMwVjzsSdL09vM8xg4fSofGen7YIj6sVUgs2X4AdTzm/ sha512-6J9O/4UFL8undkyqbEGAlNnEjm5N8Us5k9rJdcjhZwYKt1UIKXapw8f320k3tv1N0GB0Wt4xO6EKD9MsrIkCSw==" - }, - "light.js": { - "src": "public/build/light.js", - "integrity": "sha256-TDwcqlc6Pc8yTRNLOk0jp3Wnp06cnF/OSaFvwOnwCVQ= sha384-xJv6KB8Qm9Skru8PY5CRdU9a59e94tcYWonpy5rqaci6fiecQM3aH+eHe41succB sha512-sPXciUAa7rpRofN6b93v9+tWpLP88ER6TqAGpbHkz69mxuQLJv1p580fEfxLeIs3HfLkDXnVPK7rGF0d5Evrog==" - }, - "moment-node_modules_moment_locale_af_js-node_modules_moment_locale_ar-dz_js-node_modules_mome-ddcc36.js": { - "src": "public/build/moment-node_modules_moment_locale_af_js-node_modules_moment_locale_ar-dz_js-node_modules_mome-ddcc36.js", - "integrity": "sha256-BvrNPeEZbt43PpUlZ4r5pUougn3OzHIHKZN9pa5fotw= sha384-gMrZ0j/kyHwx2bhyt5ZjZOPlK7P7TRN6rBnk/0c9iF2X83GVTWNjhHcldmlYJXnf sha512-B+39Sk+Rwdo9hURcDoWL/61/sbMsTlqfES+FCsMC1r2xUH/Fyw/U2H0CgIi+c3AJyu1GLEvp//QBN0RZxaizhw==" - }, - "runtime.js": { - "src": "public/build/runtime.js", - "integrity": "sha256-tM4AGASn3Cb8139+wp3w6rlo3ELFAuUW7K4Pifx226o= sha384-DfxxsYWb0+RxiXOr+wtCSzAAYGecffq/iHyn6CN9tHmaORv1sS+rsrnlnJo2jPQD sha512-qSxdqrx0mJLY1mdkbKrkCyqOoIEgFqzCoY9+uIuFRIVDPFbb2nJy0NtaKMQvDJnAzIrJFwzwW1e250T4WqQNiQ==" - }, - "swagger.css": { - "src": "public/build/grafana.swagger.2733d417270d5dd49373.css", - "integrity": "sha256-GNcHNgIAT7S+J4X7seFjlvNPC1bRhM15d0cQBm3VFoQ= sha384-ywztCBf8uF0tTFjC1mLth33RI2WuFURN3dRy7Bv2PheGzbWJpwlgo9+mtT2Zm7mO sha512-e4c+VedZGqcwLqwfdqRWonggRPO0gjJ7Z0YbXK5z4bFTsUIc+x8ycIJG+eQaf8cuHlsakG4hkWNkRwLBazcFAg==" - }, - "swagger.js": { - "src": "public/build/swagger.js", - "integrity": "sha256-wLlip7zRYODW/TPcI5JZPRdmWirc1KD+UcNF+8V9RBk= sha384-6VGD+LgCpjMZN/ORSjWcrWa9diUzQO3OfEhP0D2ZluSwP4IT+0kH7KEeD9NVbojd sha512-vZOCFzBZBhd34yGv8z7P4Gw4WLVR9HjpuK0y6Kcw+pCBk5Dv9qHBg3ZVs6s0tOnUmiMWwgL4Ne8f+zgiuJVPqg==" + "1085.ab25b0e84da80ffc7244.js": { + "src": "public/build/grafana.app.ab25b0e84da80ffc7244.css", + "integrity": "sha384-CCCC" }, "entrypoints": { "app": { "assets": { "js": [ - "public/build/runtime.js", - "public/build/default-packages_grafana-ui_src_components_Layout_Stack_Stack_tsx-packages_grafana-ui_src_com-2a3620.js", - "public/build/app.js" + "public/build/runtime.d0d77f9ceac402028085.js", + "public/build/6029.0549a3fcb50e73c4b256.js" ], - "css": ["public/build/grafana.app.91aaa9d81398c147a57c.css"] - } - }, - "swagger": { - "assets": { - "js": [ - "public/build/runtime.js", - "public/build/swagger.js" - ], - "css": ["public/build/grafana.swagger.2733d417270d5dd49373.css"] + "css": [ + "public/build/grafana.app.ab25b0e84da80ffc7244.css" + ] } }, "dark": { "assets": { - "js": ["public/build/runtime.js", "public/build/dark.js"], - "css": ["public/build/grafana.dark.722d809dba5a31f57d49.css"] + "js": [ + "public/build/runtime.d0d77f9ceac402028085.js", + "public/build/dark.d9196c1e81619cd5ae4f.js" + ], + "css": [ + "public/build/grafana.dark.23c5425b7a9e1580d499.css" + ] } }, "light": { "assets": { - "js": ["public/build/runtime.js", "public/build/light.js"], - "css": ["public/build/grafana.light.2fbd901d840329c18394.css"] + "js": [ + "public/build/runtime.d0d77f9ceac402028085.js", + "public/build/light.6f4baca9576edc9c2e5b.js" + ], + "css": [ + "public/build/grafana.light.7fb85ddc153a7c559092.css" + ] } } } -} +} \ No newline at end of file diff --git a/pkg/api/webassets/webassets.go b/pkg/api/webassets/webassets.go index b0cd7ab09c9..bdab5665ba6 100644 --- a/pkg/api/webassets/webassets.go +++ b/pkg/api/webassets/webassets.go @@ -20,10 +20,9 @@ type ManifestInfo struct { Integrity string `json:"integrity,omitempty"` // The known entrypoints - App *EntryPointInfo `json:"app,omitempty"` - Dark *EntryPointInfo `json:"dark,omitempty"` - Light *EntryPointInfo `json:"light,omitempty"` - Swagger *EntryPointInfo `json:"swagger,omitempty"` + App *EntryPointInfo `json:"app,omitempty"` + Dark *EntryPointInfo `json:"dark,omitempty"` + Light *EntryPointInfo `json:"light,omitempty"` } type EntryPointInfo struct { @@ -38,7 +37,7 @@ var ( entryPointAssetsCache *dtos.EntryPointAssets // TODO: get rid of global state ) -func GetWebAssets(ctx context.Context, cfg *setting.Cfg, license licensing.Licensing) (*dtos.EntryPointAssets, error) { +func GetWebAssets(ctx context.Context, build string, cfg *setting.Cfg, license licensing.Licensing) (*dtos.EntryPointAssets, error) { entryPointAssetsCacheMu.RLock() ret := entryPointAssetsCache entryPointAssetsCacheMu.RUnlock() @@ -54,11 +53,11 @@ func GetWebAssets(ctx context.Context, cfg *setting.Cfg, license licensing.Licen cdn := "" // "https://grafana-assets.grafana.net/grafana/10.3.0-64123/" if cdn != "" { - result, err = readWebAssetsFromCDN(ctx, cdn) + result, err = readWebAssetsFromCDN(ctx, build, cdn) } if result == nil { - result, err = readWebAssetsFromFile(filepath.Join(cfg.StaticRootPath, "build", "assets-manifest.json")) + result, err = readWebAssetsFromFile(filepath.Join(cfg.StaticRootPath, build, "assets-manifest.json")) if err == nil { cdn, _ = cfg.GetContentDeliveryURL(license.ContentDeliveryPrefix()) if cdn != "" { @@ -83,8 +82,8 @@ func readWebAssetsFromFile(manifestpath string) (*dtos.EntryPointAssets, error) return readWebAssets(f) } -func readWebAssetsFromCDN(ctx context.Context, baseURL string) (*dtos.EntryPointAssets, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL+"public/build/assets-manifest.json", nil) +func readWebAssetsFromCDN(ctx context.Context, build string, baseURL string) (*dtos.EntryPointAssets, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL+"public/"+build+"/assets-manifest.json", nil) if err != nil { return nil, err } @@ -123,23 +122,16 @@ func readWebAssets(r io.Reader) (*dtos.EntryPointAssets, error) { if entryPoints.App == nil || len(entryPoints.App.Assets.JS) == 0 { return nil, fmt.Errorf("missing app entry, try running `yarn build`") } - if entryPoints.Dark == nil || len(entryPoints.Dark.Assets.CSS) == 0 { - return nil, fmt.Errorf("missing dark entry, try running `yarn build`") - } - if entryPoints.Light == nil || len(entryPoints.Light.Assets.CSS) == 0 { - return nil, fmt.Errorf("missing light entry, try running `yarn build`") - } - if entryPoints.Swagger == nil || len(entryPoints.Swagger.Assets.JS) == 0 { - return nil, fmt.Errorf("missing swagger entry, try running `yarn build`") - } rsp := &dtos.EntryPointAssets{ - JSFiles: make([]dtos.EntryPointAsset, 0, len(entryPoints.App.Assets.JS)), - CSSFiles: make([]dtos.EntryPointAsset, 0, len(entryPoints.App.Assets.CSS)), - Dark: entryPoints.Dark.Assets.CSS[0], - Light: entryPoints.Light.Assets.CSS[0], - Swagger: make([]dtos.EntryPointAsset, 0, len(entryPoints.Swagger.Assets.JS)), - SwaggerCSSFiles: make([]dtos.EntryPointAsset, 0, len(entryPoints.Swagger.Assets.CSS)), + JSFiles: make([]dtos.EntryPointAsset, 0, len(entryPoints.App.Assets.JS)), + CSSFiles: make([]dtos.EntryPointAsset, 0, len(entryPoints.App.Assets.CSS)), + } + if entryPoints.Dark != nil && len(entryPoints.Dark.Assets.CSS) > 0 { + rsp.Dark = entryPoints.Dark.Assets.CSS[0] + } + if entryPoints.Light != nil && len(entryPoints.Light.Assets.CSS) > 0 { + rsp.Light = entryPoints.Light.Assets.CSS[0] } for _, entry := range entryPoints.App.Assets.JS { @@ -154,17 +146,5 @@ func readWebAssets(r io.Reader) (*dtos.EntryPointAssets, error) { Integrity: integrity[entry], }) } - for _, entry := range entryPoints.Swagger.Assets.JS { - rsp.Swagger = append(rsp.Swagger, dtos.EntryPointAsset{ - FilePath: entry, - Integrity: integrity[entry], - }) - } - for _, entry := range entryPoints.Swagger.Assets.CSS { - rsp.SwaggerCSSFiles = append(rsp.SwaggerCSSFiles, dtos.EntryPointAsset{ - FilePath: entry, - Integrity: integrity[entry], - }) - } return rsp, nil } diff --git a/pkg/api/webassets/webassets_test.go b/pkg/api/webassets/webassets_test.go index b4bb5c25d70..65e89217285 100644 --- a/pkg/api/webassets/webassets_test.go +++ b/pkg/api/webassets/webassets_test.go @@ -3,7 +3,6 @@ package webassets import ( "context" "encoding/json" - "fmt" "testing" "github.com/stretchr/testify/require" @@ -16,101 +15,32 @@ func TestReadWebassets(t *testing.T) { dto, err := json.MarshalIndent(assets, "", " ") require.NoError(t, err) // fmt.Printf("%s\n", string(dto)) - require.JSONEq(t, `{ - "jsFiles": [ - { - "filePath": "public/build/runtime.js", - "integrity": "sha256-tM4AGASn3Cb8139+wp3w6rlo3ELFAuUW7K4Pifx226o= sha384-DfxxsYWb0+RxiXOr+wtCSzAAYGecffq/iHyn6CN9tHmaORv1sS+rsrnlnJo2jPQD sha512-qSxdqrx0mJLY1mdkbKrkCyqOoIEgFqzCoY9+uIuFRIVDPFbb2nJy0NtaKMQvDJnAzIrJFwzwW1e250T4WqQNiQ==" - }, - { - "filePath": "public/build/default-packages_grafana-ui_src_components_Layout_Stack_Stack_tsx-packages_grafana-ui_src_com-2a3620.js", - "integrity": "sha256-+0bPuBGKFGglkXvW4oPiolrNveozRLZVLUrbCYsbVcM= sha384-EIayAgykdDWmyilAuXo4ad96v3tRqdWZp+BHdeDpSSsbAMeg+eoBBbk2Yh219kDg sha512-+jn7kmQ9Id8aTIe66TD+vM+W19cTIVexEfkxbxgqXdJyJ72qalN6ccWyP1ro1w/E1R/laZGNLz1LBc1I4u2Isw==" - }, - { - "filePath": "public/build/app.js", - "integrity": "sha256-IOZKp3piC3vddDXP5jy5rIw0vb0KKEOg/k9EGrIxskk= sha384-CBNr5W0pJ23LQMnz5BZI1iVBIExOmF/wpqkEnBtYu9R/yYJIzjpv8KT0a3TBulOi sha512-ockzlzgosuZvLittZrSzh8lexEIZF9iKpy6J9Ii4es3e4D34FpWhHJhDZGpxLVraX4ypLofrDp2Yy0sdUbdi7w==" - } - ], - "cssFiles": [ - { - "filePath": "public/build/grafana.app.91aaa9d81398c147a57c.css", - "integrity": "sha256-77rfikk+dYkH82TOmcmleVoDOHZQdhzVX9gDLcgPbtQ= sha384-IOTlZ1IvTVq5ekKLoaE3/SoZ12K1eExOAnSw9BzkgQ3+RcyQpb1S5hO2w//IIkRB sha512-0Ct3uJBFQIkyxYTvMxseA1cphe2RivXQ2MCbiV0hEm5NzWPiY9sq2P4ay5dXz5v35c++4W47KaknoWlc83bQJQ==" - } - ], - "dark": "public/build/grafana.dark.722d809dba5a31f57d49.css", - "light": "public/build/grafana.light.2fbd901d840329c18394.css", - "swagger": [ - { - "filePath": "public/build/runtime.js", - "integrity": "sha256-tM4AGASn3Cb8139+wp3w6rlo3ELFAuUW7K4Pifx226o= sha384-DfxxsYWb0+RxiXOr+wtCSzAAYGecffq/iHyn6CN9tHmaORv1sS+rsrnlnJo2jPQD sha512-qSxdqrx0mJLY1mdkbKrkCyqOoIEgFqzCoY9+uIuFRIVDPFbb2nJy0NtaKMQvDJnAzIrJFwzwW1e250T4WqQNiQ==" - }, - { - "filePath": "public/build/swagger.js", - "integrity": "sha256-wLlip7zRYODW/TPcI5JZPRdmWirc1KD+UcNF+8V9RBk= sha384-6VGD+LgCpjMZN/ORSjWcrWa9diUzQO3OfEhP0D2ZluSwP4IT+0kH7KEeD9NVbojd sha512-vZOCFzBZBhd34yGv8z7P4Gw4WLVR9HjpuK0y6Kcw+pCBk5Dv9qHBg3ZVs6s0tOnUmiMWwgL4Ne8f+zgiuJVPqg==" - } - ], - "swaggerCssFiles": [ - { - "filePath": "public/build/grafana.swagger.2733d417270d5dd49373.css", - "integrity": "sha256-GNcHNgIAT7S+J4X7seFjlvNPC1bRhM15d0cQBm3VFoQ= sha384-ywztCBf8uF0tTFjC1mLth33RI2WuFURN3dRy7Bv2PheGzbWJpwlgo9+mtT2Zm7mO sha512-e4c+VedZGqcwLqwfdqRWonggRPO0gjJ7Z0YbXK5z4bFTsUIc+x8ycIJG+eQaf8cuHlsakG4hkWNkRwLBazcFAg==" - } - ] - }`, string(dto)) - - assets.SetContentDeliveryURL("https://grafana-assets.grafana.net/grafana/10.3.0-64123/") - - dto, err = json.MarshalIndent(assets, "", " ") - require.NoError(t, err) - fmt.Printf("%s\n", string(dto)) - - require.JSONEq(t, `{ - "cdn": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/", - "jsFiles": [ - { - "filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/runtime.js", - "integrity": "sha256-tM4AGASn3Cb8139+wp3w6rlo3ELFAuUW7K4Pifx226o= sha384-DfxxsYWb0+RxiXOr+wtCSzAAYGecffq/iHyn6CN9tHmaORv1sS+rsrnlnJo2jPQD sha512-qSxdqrx0mJLY1mdkbKrkCyqOoIEgFqzCoY9+uIuFRIVDPFbb2nJy0NtaKMQvDJnAzIrJFwzwW1e250T4WqQNiQ==" - }, - { - "filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/default-packages_grafana-ui_src_components_Layout_Stack_Stack_tsx-packages_grafana-ui_src_com-2a3620.js", - "integrity": "sha256-+0bPuBGKFGglkXvW4oPiolrNveozRLZVLUrbCYsbVcM= sha384-EIayAgykdDWmyilAuXo4ad96v3tRqdWZp+BHdeDpSSsbAMeg+eoBBbk2Yh219kDg sha512-+jn7kmQ9Id8aTIe66TD+vM+W19cTIVexEfkxbxgqXdJyJ72qalN6ccWyP1ro1w/E1R/laZGNLz1LBc1I4u2Isw==" - }, - { - "filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/app.js", - "integrity": "sha256-IOZKp3piC3vddDXP5jy5rIw0vb0KKEOg/k9EGrIxskk= sha384-CBNr5W0pJ23LQMnz5BZI1iVBIExOmF/wpqkEnBtYu9R/yYJIzjpv8KT0a3TBulOi sha512-ockzlzgosuZvLittZrSzh8lexEIZF9iKpy6J9Ii4es3e4D34FpWhHJhDZGpxLVraX4ypLofrDp2Yy0sdUbdi7w==" - } - ], - "cssFiles": [ - { - "filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/grafana.app.91aaa9d81398c147a57c.css", - "integrity": "sha256-77rfikk+dYkH82TOmcmleVoDOHZQdhzVX9gDLcgPbtQ= sha384-IOTlZ1IvTVq5ekKLoaE3/SoZ12K1eExOAnSw9BzkgQ3+RcyQpb1S5hO2w//IIkRB sha512-0Ct3uJBFQIkyxYTvMxseA1cphe2RivXQ2MCbiV0hEm5NzWPiY9sq2P4ay5dXz5v35c++4W47KaknoWlc83bQJQ==" - } - ], - "dark": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/grafana.dark.722d809dba5a31f57d49.css", - "light": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/grafana.light.2fbd901d840329c18394.css", - "swagger": [ - { - "filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/runtime.js", - "integrity": "sha256-tM4AGASn3Cb8139+wp3w6rlo3ELFAuUW7K4Pifx226o= sha384-DfxxsYWb0+RxiXOr+wtCSzAAYGecffq/iHyn6CN9tHmaORv1sS+rsrnlnJo2jPQD sha512-qSxdqrx0mJLY1mdkbKrkCyqOoIEgFqzCoY9+uIuFRIVDPFbb2nJy0NtaKMQvDJnAzIrJFwzwW1e250T4WqQNiQ==" - }, - { - "filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/swagger.js", - "integrity": "sha256-wLlip7zRYODW/TPcI5JZPRdmWirc1KD+UcNF+8V9RBk= sha384-6VGD+LgCpjMZN/ORSjWcrWa9diUzQO3OfEhP0D2ZluSwP4IT+0kH7KEeD9NVbojd sha512-vZOCFzBZBhd34yGv8z7P4Gw4WLVR9HjpuK0y6Kcw+pCBk5Dv9qHBg3ZVs6s0tOnUmiMWwgL4Ne8f+zgiuJVPqg==" - } - ], - "swaggerCssFiles": [ - { - "filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/grafana.swagger.2733d417270d5dd49373.css", - "integrity": "sha256-GNcHNgIAT7S+J4X7seFjlvNPC1bRhM15d0cQBm3VFoQ= sha384-ywztCBf8uF0tTFjC1mLth33RI2WuFURN3dRy7Bv2PheGzbWJpwlgo9+mtT2Zm7mO sha512-e4c+VedZGqcwLqwfdqRWonggRPO0gjJ7Z0YbXK5z4bFTsUIc+x8ycIJG+eQaf8cuHlsakG4hkWNkRwLBazcFAg==" - } - ] - }`, string(dto)) + "jsFiles": [ + { + "filePath": "public/build/runtime.d0d77f9ceac402028085.js", + "integrity": "sha384-AAAA" + }, + { + "filePath": "public/build/6029.0549a3fcb50e73c4b256.js", + "integrity": "sha384-BBBB" + } + ], + "cssFiles": [ + { + "filePath": "public/build/grafana.app.ab25b0e84da80ffc7244.css", + "integrity": "sha384-CCCC" + } + ], + "dark": "public/build/grafana.dark.23c5425b7a9e1580d499.css", + "light": "public/build/grafana.light.7fb85ddc153a7c559092.css" + }`, string(dto)) } func TestReadWebassetsFromCDN(t *testing.T) { t.Skip() - assets, err := readWebAssetsFromCDN(context.Background(), "https://grafana-assets.grafana.net/grafana/10.3.0-64123/") + assets, err := readWebAssetsFromCDN(context.Background(), "build", "https://grafana-assets.grafana.net/grafana/10.3.0-64123/") require.NoError(t, err) dto, err := json.MarshalIndent(assets, "", " ") diff --git a/pkg/middleware/recovery.go b/pkg/middleware/recovery.go index 01e07ee8e38..63c08172c9b 100644 --- a/pkg/middleware/recovery.go +++ b/pkg/middleware/recovery.go @@ -138,7 +138,7 @@ func Recovery(cfg *setting.Cfg, license licensing.Licensing) web.Middleware { return } - assets, _ := webassets.GetWebAssets(req.Context(), cfg, license) + assets, _ := webassets.GetWebAssets(req.Context(), "build", cfg, license) if assets == nil { assets = &dtos.EntryPointAssets{JSFiles: []dtos.EntryPointAsset{}} } diff --git a/pkg/services/frontend/index.go b/pkg/services/frontend/index.go index 4f02bbe1127..037843e0357 100644 --- a/pkg/services/frontend/index.go +++ b/pkg/services/frontend/index.go @@ -49,7 +49,7 @@ var ( ) func NewIndexProvider(cfg *setting.Cfg, license licensing.Licensing) (*IndexProvider, error) { - assets, err := webassets.GetWebAssets(context.Background(), cfg, license) + assets, err := webassets.GetWebAssets(context.Background(), "build", cfg, license) if err != nil { return nil, err } diff --git a/project.json b/project.json index b67053dbbe6..62ac256bd91 100644 --- a/project.json +++ b/project.json @@ -5,6 +5,7 @@ "targets": { "start": { "dependsOn": [ + "swaggerui:build", "themes-generate", { "projects": ["tag:scope:plugin"], @@ -14,6 +15,7 @@ }, "build": { "dependsOn": [ + "swaggerui:build", "themes-generate", { "projects": ["tag:scope:plugin"], @@ -35,6 +37,15 @@ "{workspaceRoot}/public/sass/_variables.light.generated.scss" ], "cache": true + }, + "swaggerui:build": { + "inputs": [ + "{workspaceRoot}/packages/grafana-ui/src/themes/**", + "{workspaceRoot}/public/app/core/trustedTypePolicies.ts", + "{workspaceRoot}/public/swagger/**" + ], + "outputs": ["{workspaceRoot}/public/build-swagger"], + "cache": true } } } diff --git a/public/views/swagger.html b/public/views/swagger.html index 44e622e0087..464fad8a7f5 100644 --- a/public/views/swagger.html +++ b/public/views/swagger.html @@ -12,10 +12,9 @@ Grafana API Reference - [[range $asset := .Assets.SwaggerCSSFiles]] + [[range $asset := .Assets.CSSFiles]] [[end]] - @@ -27,11 +26,11 @@
- [[range $asset := .Assets.Swagger]] + [[range $asset := .Assets.JSFiles]] [[end]] diff --git a/scripts/webpack/webpack.common.js b/scripts/webpack/webpack.common.js index 833438bd51a..37e8c399066 100644 --- a/scripts/webpack/webpack.common.js +++ b/scripts/webpack/webpack.common.js @@ -7,7 +7,6 @@ module.exports = { target: 'web', entry: { app: './public/app/index.ts', - swagger: './public/swagger/index.tsx', }, experiments: { // Required to load WASM modules. diff --git a/scripts/webpack/webpack.swagger.js b/scripts/webpack/webpack.swagger.js new file mode 100644 index 00000000000..48c3056877d --- /dev/null +++ b/scripts/webpack/webpack.swagger.js @@ -0,0 +1,27 @@ +// @ts-check +const path = require('path'); + +const makeBaseConfig = require('./webpack.prod.js'); + +module.exports = (env = {}) => { + const baseConfig = makeBaseConfig(env); + + return { + ...baseConfig, + + entry: { + app: './public/swagger/index.tsx', + light: './public/sass/grafana.light.scss', + }, + + // Output to a different directory so each build doesn't clobber each other + output: { + ...baseConfig.output, + clean: true, + path: path.resolve(__dirname, '../../public/build-swagger'), + filename: '[name].[contenthash].js', + // Keep publicPath relative for host.com/grafana/ deployments + publicPath: 'public/build-swagger/', + }, + }; +}; From fe9254b333fc04d99e10e31ac40ea3dd96b008a5 Mon Sep 17 00:00:00 2001 From: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com> Date: Fri, 25 Apr 2025 07:24:07 -0600 Subject: [PATCH 125/146] Chore: Update release strategy docs (#104488) * baldm0mma/ update release docs * baldm0mma/ run prettier * baldm0mma/ change back apostrophies * baldm0mma/ update to or * baldm0mma/ revert characters --- .../upgrade-guide/when-to-upgrade/index.md | 89 +++++++++++++------ 1 file changed, 60 insertions(+), 29 deletions(-) diff --git a/docs/sources/upgrade-guide/when-to-upgrade/index.md b/docs/sources/upgrade-guide/when-to-upgrade/index.md index 4c6f9602f04..0a464e96f5d 100644 --- a/docs/sources/upgrade-guide/when-to-upgrade/index.md +++ b/docs/sources/upgrade-guide/when-to-upgrade/index.md @@ -46,22 +46,30 @@ We provide release documentation in multiple places to address different needs: ## When to expect releases -Currently, Grafana is on a monthly release cycle. Here’s a look at scheduled releases for the first half of 2025: +Currently, Grafana is on a monthly release cycle. Here’s a look at scheduled releases for 2025: -| **Anticipated release date** | **Grafana versions** | **Release type** | -| ---------------------------- | --------------------------------- | ---------------- | -| Jan. 28, 2025 | 11.5 & Supported versions | Minor & patching | -| Feb. 18, 2025 | Supported versions | Patching | -| March 25, 2025 | 11.6 & Supported versions | Minor & patching | -| April 15, 2025 | Supported versions | Patching | -| May 5, 2025 | Grafana 12.0 & Supported versions | Major & patching | +| **Release date** | **Grafana versions** | **Release type** | +| ---------------- | ------------------------- | ---------------- | +| Jan. 28, 2025 | 11.5 & Supported versions | Minor & patching | +| Feb. 18, 2025 | Supported versions | Patching | +| March 25, 2025 | 11.6 & Supported versions | Minor & patching | +| April 23, 2025 | Supported versions | Patching | +| May 5, 2025 | Grafana 12.0 | Major only | +| May 20, 2025 | Supported versions | Patching | +| June 17, 2025 | Supported versions | Patching | +| July 22, 2025 | 12.1 & Supported versions | Minor & patching | +| Aug. 12, 2025 | Supported versions | Patching | +| Sept. 23, 2025 | 12.2 & Supported versions | Minor & patching | +| Oct. 21, 2025 | Supported versions | Patching | +| Nov. 18, 2025 | 12.3 & Supported versions | Minor & patching | +| Dec. 16, 2025 | Supported versions | Patching | ### A few important notes - The schedule above outlines how we plan release dates. However, unforeseen events and circumstances may cause dates to change. - High severity security and feature degradation incidents will result in ad-hoc releases that are not scheduled ahead of time. - Patching releases are for the current (last released) minor version of Grafana. Additional older versions of Grafana may be included if there is a critical bug or security vulnerability that needs to be patched. -- Release freezes: Each year Grafana implements two release freezes to accommodate for the holiday season, these dates will be announced as the holiday season approaches. During these times, no scheduled releases will be executed. However, this does not apply to changes that may be required during the course of an operational or security incident. +- Release freezes: Each year Grafana implements two release freezes to accommodate for the holiday season. During these times, no scheduled releases will be executed. However, this does not apply to changes that may be required during the course of an operational or security incident. ## Grafana security releases: improved version naming convention @@ -81,34 +89,57 @@ This naming convention should make it easier to identify security updates and th ## What to know about version support -Self-managed Grafana users have control over when they upgrade to a new version of Grafana. To help you make an informed decision about whether it's time to upgrade, it’s important that you understand the level of support provided for your current version. +Self-managed Grafana users have control over when they upgrade to a new version of Grafana. To help you make an informed decision about whether it’s time to upgrade, it’s important that you understand the level of support provided for your current version. -For self-managed Grafana (both Enterprise and OSS), the support for versions is as follows: +For self-managed Grafana (both Enterprise and OSS), the support for versions follows these rules: -- Support for each minor release extends to nine months after the release date. -- Support for the last minor release of a major version is extended an additional six months, for a total of 15 months of support after the release date. +- Each minor release is supported for 9 months after its release date +- The last minor release of a major version receives extended support for 15 months after its release date +- Support levels change as new versions are released: + - **Full Support**: The most recently released major/minor (and the last minor of the previous major) version receive full support including new features, bug fixes, and security patches + - **Security & Critical Bugs Only**: Versions that are not the most recently released major/minor (or the last minor of the previous major) version, but still within their support period, receive only security patches and critical bug fixes + - **Not Supported**: Versions beyond their support period receive no updates -Here is an overview of projected version support through 2025: +Here is an overview of version support through 2026: -| **Version** | **Release date** | **Support end of life (EOL)** | -| ----------------------- | ---------------- | --------------------------------------- | -| 10.4 (Last minor of 10) | March 2024 | June 2025 (extended support) | -| 11.0 | May 2024 | NO LONGER SUPPORTED as of February 2025 | -| 11.1 | June 2024 | NO LONGER SUPPORTED as of March 2025 | -| 11.2 | August 2024 | May 2025 | -| 11.3 | October 2024 | July 2025 | -| 11.4 | December 2024 | September 2025 | -| 11.5 | January 2025 | October 2025 | -| 11.6 (Last minor of 11) | March 2025 | June 2026 | -| 12.0 | May 2025 | January 2026 | +| **Version** | **Release date** | **Support end date** | **Support level** | +| ------------------------- | ------------------ | -------------------- | ----------------------------- | +| 9.5.x (Last minor of 9) | April 26, 2023 | July 26, 2024 | Supported for Azure Only | +| 10.0.x | June 13, 2023 | March 13, 2024 | Not Supported | +| 10.1.x | August 22, 2023 | May 22, 2024 | Not Supported | +| 10.2.x | October 24, 2023 | July 24, 2024 | Not Supported | +| 10.3.x | January 23, 2024 | October 23, 2024 | Not Supported | +| 10.4.x (Last minor of 10) | March 5, 2024 | June 5, 2025 | Security & Critical Bugs Only | +| 11.0.x | May 14, 2024 | February 14, 2025 | Security & Critical Bugs Only | +| 11.1.x | June 25, 2024 | April 23, 2025 | Security & Critical Bugs Only | +| 11.2.x | August 27, 2024 | May 27, 2025 | Security & Critical Bugs Only | +| 11.3.x | October 22, 2024 | July 22, 2025 | Security & Critical Bugs Only | +| 11.4.x | December 5, 2024 | September 5, 2025 | Security & Critical Bugs Only | +| 11.5.x | January 28, 2025 | October 28, 2025 | Security & Critical Bugs Only | +| 11.6.x (Last minor of 11) | March 25, 2025 | May 25, 2026 | Full Support | +| 12.0.x | May 5, 2025 | February 5, 2026 | Full Support until next minor | +| 12.1.x | July 22, 2025 | April 22, 2026 | Full Support until next minor | +| 12.2.x | September 23, 2025 | June 23, 2026 | Full Support until next minor | +| 12.3.x | November 18, 2025 | August 18, 2026 | Full Support until next minor | ## How are these versions supported? -The level of support changes as new versions of Grafana are released. Here are a few details to keep in mind: +The level of support changes as new versions of Grafana are released. Here are the key details: -- The current (most recently released) version of Grafana gets the highest level of support. Releases for this version include all the new features along with all bug fixes. -- All supported versions receive security patches for vulnerabilities impacting that version. -- All supported versions receive patches for bugs that cause critical feature degradation incidents. +- **Full Support**: + + - All new features + - All bug fixes + - Security patches + - Regular updates + +- **Security & Critical Bugs Only**: + + - Security vulnerability patches + - Critical bug fixes that cause feature degradation + - No new features + +- **Not Supported**: Versions beyond their support period receive no updates and should be upgraded. Keeping all this in mind, users that want to receive the most recent features and all bug fixes should be on the current (most recently released) version of Grafana. From 3ba112673f7f4f5c1eb49d4adf5ebbc09145e243 Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Fri, 25 Apr 2025 14:25:10 +0100 Subject: [PATCH 126/146] Docker: Remove pkg/apis/folder from COPY (#104524) --- Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d6869491704..4ec06a8b9ea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -70,7 +70,6 @@ COPY .citools/swagger .citools/swagger # Include vendored dependencies COPY pkg/util/xorm pkg/util/xorm -COPY pkg/apis/folder pkg/apis/folder COPY pkg/apis/secret pkg/apis/secret COPY pkg/apiserver pkg/apiserver COPY pkg/apimachinery pkg/apimachinery From cdfd3caba879ddd6e1133a82df219e099617b9a3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 14:49:50 +0100 Subject: [PATCH 127/146] Update dependency cypress to v14 (#99916) * Update dependency cypress to v14 * update drone image --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Ashley Harrison --- .drone.yml | 42 ++-- package.json | 2 +- scripts/drone/utils/images.star | 2 +- yarn.lock | 370 ++++++++++---------------------- 4 files changed, 132 insertions(+), 284 deletions(-) diff --git a/.drone.yml b/.drone.yml index 59f88baab41..d27ffd886ad 100644 --- a/.drone.yml +++ b/.drone.yml @@ -134,7 +134,7 @@ steps: environment: HOST: start-storybook PORT: "9001" - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-storybook-suite trigger: event: @@ -561,7 +561,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-dashboards-suite - commands: - ./bin/build e2e-tests --port 3001 --suite old-arch/dashboards-suite @@ -570,7 +570,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-old-arch/dashboards-suite - commands: - ./bin/build e2e-tests --port 3001 --suite smoke-tests-suite @@ -579,7 +579,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-smoke-tests-suite - commands: - ./bin/build e2e-tests --port 3001 --suite old-arch/smoke-tests-suite @@ -588,7 +588,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-old-arch/smoke-tests-suite - commands: - ./bin/build e2e-tests --port 3001 --suite panels-suite @@ -597,7 +597,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-panels-suite - commands: - ./bin/build e2e-tests --port 3001 --suite old-arch/panels-suite @@ -606,7 +606,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-old-arch/panels-suite - commands: - ./bin/build e2e-tests --port 3001 --suite various-suite @@ -615,7 +615,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-various-suite - commands: - ./bin/build e2e-tests --port 3001 --suite old-arch/various-suite @@ -624,7 +624,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-old-arch/various-suite - commands: - GITHUB_TOKEN=$(cat /github-app/token) @@ -1658,7 +1658,7 @@ steps: environment: HOST: start-storybook PORT: "9001" - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-storybook-suite trigger: branch: main @@ -1851,7 +1851,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-dashboards-suite - commands: - ./bin/build e2e-tests --port 3001 --suite old-arch/dashboards-suite @@ -1860,7 +1860,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-old-arch/dashboards-suite - commands: - ./bin/build e2e-tests --port 3001 --suite smoke-tests-suite @@ -1869,7 +1869,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-smoke-tests-suite - commands: - ./bin/build e2e-tests --port 3001 --suite old-arch/smoke-tests-suite @@ -1878,7 +1878,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-old-arch/smoke-tests-suite - commands: - ./bin/build e2e-tests --port 3001 --suite panels-suite @@ -1887,7 +1887,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-panels-suite - commands: - ./bin/build e2e-tests --port 3001 --suite old-arch/panels-suite @@ -1896,7 +1896,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-old-arch/panels-suite - commands: - ./bin/build e2e-tests --port 3001 --suite various-suite @@ -1905,7 +1905,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-various-suite - commands: - ./bin/build e2e-tests --port 3001 --suite old-arch/various-suite @@ -1914,7 +1914,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-old-arch/various-suite - commands: - GITHUB_TOKEN=$(cat /github-app/token) @@ -4923,7 +4923,7 @@ steps: - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/drone-downstream - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/docker-puppeteer:1.1.0 - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/docs-base:latest - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM cypress/included:13.10.0 + - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM cypress/included:14.3.2 - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM jwilder/dockerize:0.6.1 - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM koalaman/shellcheck:stable - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM rockylinux:9 @@ -4961,7 +4961,7 @@ steps: - trivy --exit-code 1 --severity HIGH,CRITICAL grafana/drone-downstream - trivy --exit-code 1 --severity HIGH,CRITICAL grafana/docker-puppeteer:1.1.0 - trivy --exit-code 1 --severity HIGH,CRITICAL grafana/docs-base:latest - - trivy --exit-code 1 --severity HIGH,CRITICAL cypress/included:13.10.0 + - trivy --exit-code 1 --severity HIGH,CRITICAL cypress/included:14.3.2 - trivy --exit-code 1 --severity HIGH,CRITICAL jwilder/dockerize:0.6.1 - trivy --exit-code 1 --severity HIGH,CRITICAL koalaman/shellcheck:stable - trivy --exit-code 1 --severity HIGH,CRITICAL rockylinux:9 @@ -5210,6 +5210,6 @@ kind: secret name: gcr_credentials --- kind: signature -hmac: 8e25f1f786b8de4eb21dfbeca8c5fcb9701b1e62ecf98287d9225ecc6e8c29e8 +hmac: 16029e3922ae0a13a31233717aa172c06bf0e6fc8cf01f5148de62147c259ac8 ... diff --git a/package.json b/package.json index 857ac1ce10a..522cce88a0a 100644 --- a/package.json +++ b/package.json @@ -170,7 +170,7 @@ "crashme": "0.0.15", "css-loader": "7.1.2", "css-minimizer-webpack-plugin": "7.0.0", - "cypress": "13.10.0", + "cypress": "14.3.2", "cypress-file-upload": "5.0.8", "cypress-recurse": "^1.35.3", "esbuild": "0.25.0", diff --git a/scripts/drone/utils/images.star b/scripts/drone/utils/images.star index d51f9905df0..27f5cb30d47 100644 --- a/scripts/drone/utils/images.star +++ b/scripts/drone/utils/images.star @@ -31,7 +31,7 @@ images = { "drone_downstream": "grafana/drone-downstream", "docker_puppeteer": "grafana/docker-puppeteer:1.1.0", "docs": "grafana/docs-base:latest", - "cypress": "cypress/included:13.10.0", + "cypress": "cypress/included:14.3.2", "dockerize": "jwilder/dockerize:0.6.1", "shellcheck": "koalaman/shellcheck:stable", "rocky": "rockylinux:9", diff --git a/yarn.lock b/yarn.lock index e578eb3f212..ca6f02473d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -99,7 +99,7 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:7.26.10": +"@babel/core@npm:7.26.10, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.9, @babel/core@npm:^7.22.9": version: 7.26.10 resolution: "@babel/core@npm:7.26.10" dependencies: @@ -122,43 +122,7 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.9, @babel/core@npm:^7.22.9": - version: 7.26.9 - resolution: "@babel/core@npm:7.26.9" - dependencies: - "@ampproject/remapping": "npm:^2.2.0" - "@babel/code-frame": "npm:^7.26.2" - "@babel/generator": "npm:^7.26.9" - "@babel/helper-compilation-targets": "npm:^7.26.5" - "@babel/helper-module-transforms": "npm:^7.26.0" - "@babel/helpers": "npm:^7.26.9" - "@babel/parser": "npm:^7.26.9" - "@babel/template": "npm:^7.26.9" - "@babel/traverse": "npm:^7.26.9" - "@babel/types": "npm:^7.26.9" - convert-source-map: "npm:^2.0.0" - debug: "npm:^4.1.0" - gensync: "npm:^1.0.0-beta.2" - json5: "npm:^2.2.3" - semver: "npm:^6.3.1" - checksum: 10/ceed199dbe25f286a0a59a2ea7879aed37c1f3bb289375d061eda4752cab2ba365e7f9e969c7fd3b9b95c930493db6eeb5a6d6f017dd135fb5a4503449aad753 - languageName: node - linkType: hard - -"@babel/generator@npm:^7.22.9, @babel/generator@npm:^7.26.9, @babel/generator@npm:^7.7.2": - version: 7.26.9 - resolution: "@babel/generator@npm:7.26.9" - dependencies: - "@babel/parser": "npm:^7.26.9" - "@babel/types": "npm:^7.26.9" - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.25" - jsesc: "npm:^3.0.2" - checksum: 10/95075dd6158a49efcc71d7f2c5d20194fcf245348de7723ca35e37cd5800587f1d4de2be6c4ba87b5f5fbb967c052543c109eaab14b43f6a73eb05ccd9a5bb44 - languageName: node - linkType: hard - -"@babel/generator@npm:^7.26.10, @babel/generator@npm:^7.27.0": +"@babel/generator@npm:^7.22.9, @babel/generator@npm:^7.26.10, @babel/generator@npm:^7.27.0, @babel/generator@npm:^7.7.2": version: 7.27.0 resolution: "@babel/generator@npm:7.27.0" dependencies: @@ -375,16 +339,6 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.26.9": - version: 7.26.9 - resolution: "@babel/helpers@npm:7.26.9" - dependencies: - "@babel/template": "npm:^7.26.9" - "@babel/types": "npm:^7.26.9" - checksum: 10/267dfa7d04dff7720610497f466aa7b60652b7ec8dde5914527879350c9d655271e892117c5b2f0f083d92d2a8e5e2cf9832d4f98cd7fb72d78f796002af19a1 - languageName: node - linkType: hard - "@babel/highlight@npm:^7.25.7": version: 7.25.9 resolution: "@babel/highlight@npm:7.25.9" @@ -397,18 +351,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.26.9": - version: 7.26.9 - resolution: "@babel/parser@npm:7.26.9" - dependencies: - "@babel/types": "npm:^7.26.9" - bin: - parser: ./bin/babel-parser.js - checksum: 10/cb84fe3ba556d6a4360f3373cf7eb0901c46608c8d77330cc1ca021d60f5d6ebb4056a8e7f9dd0ef231923ef1fe69c87b11ce9e160d2252e089a20232a2b942b - languageName: node - linkType: hard - -"@babel/parser@npm:^7.26.10, @babel/parser@npm:^7.27.0": +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.26.10, @babel/parser@npm:^7.27.0": version: 7.27.0 resolution: "@babel/parser@npm:7.27.0" dependencies: @@ -1482,18 +1425,7 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.22.5, @babel/template@npm:^7.24.7, @babel/template@npm:^7.25.9, @babel/template@npm:^7.26.9, @babel/template@npm:^7.3.3": - version: 7.26.9 - resolution: "@babel/template@npm:7.26.9" - dependencies: - "@babel/code-frame": "npm:^7.26.2" - "@babel/parser": "npm:^7.26.9" - "@babel/types": "npm:^7.26.9" - checksum: 10/240288cebac95b1cc1cb045ad143365643da0470e905e11731e63280e43480785bd259924f4aea83898ef68e9fa7c176f5f2d1e8b0a059b27966e8ca0b41a1b6 - languageName: node - linkType: hard - -"@babel/template@npm:^7.27.0": +"@babel/template@npm:^7.22.5, @babel/template@npm:^7.24.7, @babel/template@npm:^7.25.9, @babel/template@npm:^7.26.9, @babel/template@npm:^7.27.0, @babel/template@npm:^7.3.3": version: 7.27.0 resolution: "@babel/template@npm:7.27.0" dependencies: @@ -1504,22 +1436,7 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.26.8, @babel/traverse@npm:^7.26.9": - version: 7.26.9 - resolution: "@babel/traverse@npm:7.26.9" - dependencies: - "@babel/code-frame": "npm:^7.26.2" - "@babel/generator": "npm:^7.26.9" - "@babel/parser": "npm:^7.26.9" - "@babel/template": "npm:^7.26.9" - "@babel/types": "npm:^7.26.9" - debug: "npm:^4.3.1" - globals: "npm:^11.1.0" - checksum: 10/c16a79522eafa0a7e40eb556bf1e8a3d50dbb0ff943a80f2c06cee2ec7ff87baa0c5d040a5cff574d9bcb3bed05e7d8c6f13b238a931c97267674b02c6cf45b4 - languageName: node - linkType: hard - -"@babel/traverse@npm:^7.26.10": +"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.26.10, @babel/traverse@npm:^7.26.8": version: 7.27.0 resolution: "@babel/traverse@npm:7.27.0" dependencies: @@ -1544,16 +1461,6 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.26.9": - version: 7.26.9 - resolution: "@babel/types@npm:7.26.9" - dependencies: - "@babel/helper-string-parser": "npm:^7.25.9" - "@babel/helper-validator-identifier": "npm:^7.25.9" - checksum: 10/11b62ea7ed64ef7e39cc9b33852c1084064c3b970ae0eaa5db659241cfb776577d1e68cbff4de438bada885d3a827b52cc0f3746112d8e1bc672bb99a8eb5b56 - languageName: node - linkType: hard - "@bcoe/v8-coverage@npm:^0.2.3": version: 0.2.3 resolution: "@bcoe/v8-coverage@npm:0.2.3" @@ -1785,9 +1692,9 @@ __metadata: languageName: node linkType: hard -"@cypress/request@npm:^3.0.0": - version: 3.0.1 - resolution: "@cypress/request@npm:3.0.1" +"@cypress/request@npm:^3.0.8": + version: 3.0.8 + resolution: "@cypress/request@npm:3.0.8" dependencies: aws-sign2: "npm:~0.7.0" aws4: "npm:^1.8.0" @@ -1795,19 +1702,19 @@ __metadata: combined-stream: "npm:~1.0.6" extend: "npm:~3.0.2" forever-agent: "npm:~0.6.1" - form-data: "npm:~2.3.2" - http-signature: "npm:~1.3.6" + form-data: "npm:~4.0.0" + http-signature: "npm:~1.4.0" is-typedarray: "npm:~1.0.0" isstream: "npm:~0.1.2" json-stringify-safe: "npm:~5.0.1" mime-types: "npm:~2.1.19" performance-now: "npm:^2.1.0" - qs: "npm:6.10.4" + qs: "npm:6.14.0" safe-buffer: "npm:^5.1.2" - tough-cookie: "npm:^4.1.3" + tough-cookie: "npm:^5.0.0" tunnel-agent: "npm:^0.6.0" uuid: "npm:^8.3.2" - checksum: 10/bf48bed6d6e493c05493902fb08b1d0646e7ec4300cf834816c2616f781db1a7fc447bd6f81de7c3076d738e8a6d75354e21d332f8f7ef8d9101d9b2f8e15b3a + checksum: 10/f4ee26acfed457ea017192028ff08d533052c8bae7639d8701831e691e6cd0d7d44284902feb49aa62a90c8014cf66dc2c3efc1712ad7b76e47e06f335c69981 languageName: node linkType: hard @@ -7712,20 +7619,6 @@ __metadata: languageName: node linkType: hard -"@swagger-api/apidom-ast@npm:^1.0.0-beta.5": - version: 1.0.0-beta.12 - resolution: "@swagger-api/apidom-ast@npm:1.0.0-beta.12" - dependencies: - "@babel/runtime-corejs3": "npm:^7.20.7" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.12" - "@types/ramda": "npm:~0.30.0" - ramda: "npm:~0.30.0" - ramda-adjunct: "npm:^5.0.0" - unraw: "npm:^3.0.0" - checksum: 10/adbcdb40d343eb2b18804ba82e3b0059e66fbb9df5fc43d8a968e6b8b80b68356180a430fbf237892b47a40f7713c919023dc4cd9de92b2ae7ff1c717775d30f - languageName: node - linkType: hard - "@swagger-api/apidom-core@npm:>=1.0.0-beta.13 <1.0.0-rc.0, @swagger-api/apidom-core@npm:^1.0.0-beta.30, @swagger-api/apidom-core@npm:^1.0.0-beta.5": version: 1.0.0-beta.30 resolution: "@swagger-api/apidom-core@npm:1.0.0-beta.30" @@ -7752,15 +7645,6 @@ __metadata: languageName: node linkType: hard -"@swagger-api/apidom-error@npm:^1.0.0-beta.12": - version: 1.0.0-beta.12 - resolution: "@swagger-api/apidom-error@npm:1.0.0-beta.12" - dependencies: - "@babel/runtime-corejs3": "npm:^7.20.7" - checksum: 10/644ad779bfb464b3cf1588f2ca230910782e61ca0478a5ec1a40b98a5715d976e59830d2867f3ffe1081e7c820c88d5d72dfce1ea4d3d2d8dcf2f8557b64feab - languageName: node - linkType: hard - "@swagger-api/apidom-json-pointer@npm:>=1.0.0-beta.13 <1.0.0-rc.0, @swagger-api/apidom-json-pointer@npm:^1.0.0-beta.3 <1.0.0-rc.0, @swagger-api/apidom-json-pointer@npm:^1.0.0-beta.30": version: 1.0.0-beta.30 resolution: "@swagger-api/apidom-json-pointer@npm:1.0.0-beta.30" @@ -8040,26 +7924,7 @@ __metadata: languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-json@npm:^1.0.0-beta.3 <1.0.0-rc.0, @swagger-api/apidom-parser-adapter-json@npm:^1.0.0-beta.5": - version: 1.0.0-beta.5 - resolution: "@swagger-api/apidom-parser-adapter-json@npm:1.0.0-beta.5" - dependencies: - "@babel/runtime-corejs3": "npm:^7.20.7" - "@swagger-api/apidom-ast": "npm:^1.0.0-beta.5" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.5" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.5" - "@types/ramda": "npm:~0.30.0" - node-gyp: "npm:latest" - ramda: "npm:~0.30.0" - ramda-adjunct: "npm:^5.0.0" - tree-sitter: "npm:=0.22.1" - tree-sitter-json: "npm:=0.24.8" - web-tree-sitter: "npm:=0.24.5" - checksum: 10/dc28419cbc068f5e2ecc58b0ccf807bb8717625c6043d7661d1488c054617384154d9990d6e5abed52f60209806f5b2a9eb5e20f3cd6d839290e69f6acc5871b - languageName: node - linkType: hard - -"@swagger-api/apidom-parser-adapter-json@npm:^1.0.0-beta.30": +"@swagger-api/apidom-parser-adapter-json@npm:^1.0.0-beta.3 <1.0.0-rc.0, @swagger-api/apidom-parser-adapter-json@npm:^1.0.0-beta.30, @swagger-api/apidom-parser-adapter-json@npm:^1.0.0-beta.5": version: 1.0.0-beta.30 resolution: "@swagger-api/apidom-parser-adapter-json@npm:1.0.0-beta.30" dependencies: @@ -8168,26 +8033,7 @@ __metadata: languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-yaml-1-2@npm:^1.0.0-beta.3 <1.0.0-rc.0, @swagger-api/apidom-parser-adapter-yaml-1-2@npm:^1.0.0-beta.5": - version: 1.0.0-beta.5 - resolution: "@swagger-api/apidom-parser-adapter-yaml-1-2@npm:1.0.0-beta.5" - dependencies: - "@babel/runtime-corejs3": "npm:^7.20.7" - "@swagger-api/apidom-ast": "npm:^1.0.0-beta.5" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.5" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.5" - "@tree-sitter-grammars/tree-sitter-yaml": "npm:=0.7.0" - "@types/ramda": "npm:~0.30.0" - node-gyp: "npm:latest" - ramda: "npm:~0.30.0" - ramda-adjunct: "npm:^5.0.0" - tree-sitter: "npm:=0.22.1" - web-tree-sitter: "npm:=0.24.5" - checksum: 10/8f14014d18a674447aa1e2fac4251794538f05cd86c79debcbd7cbfa7efaa3403385d292da6f96988b3d9f60b07b81e6cfce7a1bc779240513d6526160f7c116 - languageName: node - linkType: hard - -"@swagger-api/apidom-parser-adapter-yaml-1-2@npm:^1.0.0-beta.30": +"@swagger-api/apidom-parser-adapter-yaml-1-2@npm:^1.0.0-beta.3 <1.0.0-rc.0, @swagger-api/apidom-parser-adapter-yaml-1-2@npm:^1.0.0-beta.30, @swagger-api/apidom-parser-adapter-yaml-1-2@npm:^1.0.0-beta.5": version: 1.0.0-beta.30 resolution: "@swagger-api/apidom-parser-adapter-yaml-1-2@npm:1.0.0-beta.30" dependencies: @@ -9620,7 +9466,14 @@ __metadata: languageName: node linkType: hard -"@types/lodash@npm:*, @types/lodash@npm:4.17.15, @types/lodash@npm:^4.14.172": +"@types/lodash@npm:*, @types/lodash@npm:^4, @types/lodash@npm:^4.14.172": + version: 4.17.16 + resolution: "@types/lodash@npm:4.17.16" + checksum: 10/9a8bb7471a7521bd65d528e1bd14f79819a3eeb6f8a35a8a44649a7d773775c0813e93fd93bd32ccf350bb076c0bf02c6d47877c4625f526f6dd4d283c746aec + languageName: node + linkType: hard + +"@types/lodash@npm:4.17.15": version: 4.17.15 resolution: "@types/lodash@npm:4.17.15" checksum: 10/27b348b5971b9c670215331b52448a13d7d65bf1fbd320a7049c9c153c1186ff5d116ba75f05f07d32d7ece8a992b26a30c7bdc9be22a3d1e4e3e6068aa04603 @@ -9634,13 +9487,6 @@ __metadata: languageName: node linkType: hard -"@types/lodash@npm:^4": - version: 4.17.16 - resolution: "@types/lodash@npm:4.17.16" - checksum: 10/9a8bb7471a7521bd65d528e1bd14f79819a3eeb6f8a35a8a44649a7d773775c0813e93fd93bd32ccf350bb076c0bf02c6d47877c4625f526f6dd4d283c746aec - languageName: node - linkType: hard - "@types/logfmt@npm:^1.2.3": version: 1.2.6 resolution: "@types/logfmt@npm:1.2.6" @@ -12751,10 +12597,10 @@ __metadata: languageName: node linkType: hard -"ci-info@npm:^4.0.0": - version: 4.0.0 - resolution: "ci-info@npm:4.0.0" - checksum: 10/c983bb7ff1b06648f4a47432201abbd58291147d8ab5043dbb5c03e1a0e3fb2347f40d29b66a3044f28ffeb5dade01ac35aa6bd4e7464a44d9a49a3d7532415a +"ci-info@npm:^4.0.0, ci-info@npm:^4.1.0": + version: 4.2.0 + resolution: "ci-info@npm:4.2.0" + checksum: 10/928d8457f3476ffc4a66dec93b9cdf1944d5e60dba69fbd6a0fc95b652386f6ef64857f6e32372533210ef6d8954634af2c7693d7c07778ee015f3629a5e0dd9 languageName: node linkType: hard @@ -12838,16 +12684,16 @@ __metadata: languageName: node linkType: hard -"cli-table3@npm:~0.6.1": - version: 0.6.3 - resolution: "cli-table3@npm:0.6.3" +"cli-table3@npm:~0.6.5": + version: 0.6.5 + resolution: "cli-table3@npm:0.6.5" dependencies: "@colors/colors": "npm:1.5.0" string-width: "npm:^4.2.0" dependenciesMeta: "@colors/colors": optional: true - checksum: 10/8d82b75be7edc7febb1283dc49582a521536527cba80af62a2e4522a0ee39c252886a1a2f02d05ae9d753204dbcffeb3a40d1358ee10dccd7fe8d935cfad3f85 + checksum: 10/8dca71256f6f1367bab84c33add3f957367c7c43750a9828a4212ebd31b8df76bd7419d386e3391ac7419698a8540c25f1a474584028f35b170841cde2e055c5 languageName: node linkType: hard @@ -13076,7 +12922,7 @@ __metadata: languageName: node linkType: hard -"combined-stream@npm:^1.0.6, combined-stream@npm:^1.0.8, combined-stream@npm:~1.0.6": +"combined-stream@npm:^1.0.8, combined-stream@npm:~1.0.6": version: 1.0.8 resolution: "combined-stream@npm:1.0.8" dependencies: @@ -14053,11 +13899,11 @@ __metadata: languageName: node linkType: hard -"cypress@npm:13.10.0": - version: 13.10.0 - resolution: "cypress@npm:13.10.0" +"cypress@npm:14.3.2": + version: 14.3.2 + resolution: "cypress@npm:14.3.2" dependencies: - "@cypress/request": "npm:^3.0.0" + "@cypress/request": "npm:^3.0.8" "@cypress/xvfb": "npm:^1.2.4" "@types/sinonjs__fake-timers": "npm:8.1.1" "@types/sizzle": "npm:^2.3.2" @@ -14068,8 +13914,9 @@ __metadata: cachedir: "npm:^2.3.0" chalk: "npm:^4.1.0" check-more-types: "npm:^2.24.0" + ci-info: "npm:^4.1.0" cli-cursor: "npm:^3.1.0" - cli-table3: "npm:~0.6.1" + cli-table3: "npm:~0.6.5" commander: "npm:^6.2.1" common-tags: "npm:^1.8.0" dayjs: "npm:^1.10.4" @@ -14082,7 +13929,6 @@ __metadata: figures: "npm:^3.2.0" fs-extra: "npm:^9.1.0" getos: "npm:^3.2.1" - is-ci: "npm:^3.0.1" is-installed-globally: "npm:~0.4.0" lazy-ass: "npm:^1.6.0" listr2: "npm:^3.8.3" @@ -14094,14 +13940,15 @@ __metadata: process: "npm:^0.11.10" proxy-from-env: "npm:1.0.0" request-progress: "npm:^3.0.0" - semver: "npm:^7.5.3" + semver: "npm:^7.7.1" supports-color: "npm:^8.1.1" - tmp: "npm:~0.2.1" + tmp: "npm:~0.2.3" + tree-kill: "npm:1.2.2" untildify: "npm:^4.0.0" yauzl: "npm:^2.10.0" bin: cypress: bin/cypress - checksum: 10/d3dbce9a00a108d76d0f91ba419106be24a6ad2dfa3c4a3cbb70db7c053b84e9cff2465b31c77ed3a40574bdba6f908b9aa247bdc7e8115f23b622f9ef05d51d + checksum: 10/895c12d4069c47c80e3e99f7031cb54d4d708f4989bdeac50fb5a9bef43b84e3dbb274aab60b214e2fe83049eee098224a1e2830809954f8250a64f521a98aa5 languageName: node linkType: hard @@ -17024,25 +16871,15 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^4.0.0": - version: 4.0.0 - resolution: "form-data@npm:4.0.0" +"form-data@npm:^4.0.0, form-data@npm:~4.0.0": + version: 4.0.2 + resolution: "form-data@npm:4.0.2" dependencies: asynckit: "npm:^0.4.0" combined-stream: "npm:^1.0.8" + es-set-tostringtag: "npm:^2.1.0" mime-types: "npm:^2.1.12" - checksum: 10/7264aa760a8cf09482816d8300f1b6e2423de1b02bba612a136857413fdc96d7178298ced106817655facc6b89036c6e12ae31c9eb5bdc16aabf502ae8a5d805 - languageName: node - linkType: hard - -"form-data@npm:~2.3.2": - version: 2.3.3 - resolution: "form-data@npm:2.3.3" - dependencies: - asynckit: "npm:^0.4.0" - combined-stream: "npm:^1.0.6" - mime-types: "npm:^2.1.12" - checksum: 10/1b6f3ccbf4540e535887b42218a2431a3f6cfdea320119c2affa2a7a374ad8fdd1e60166fc865181f45d49b1684c3e90e7b2190d3fe016692957afb9cf0d0d02 + checksum: 10/82c65b426af4a40090e517a1bc9057f76970b4c6043e37aa49859c447d88553e77d4cc5626395079a53d2b0889ba5f2a49f3900db3ad3f3f1bf76613532572fb languageName: node linkType: hard @@ -17968,7 +17805,7 @@ __metadata: croner: "npm:^9.0.0" css-loader: "npm:7.1.2" css-minimizer-webpack-plugin: "npm:7.0.0" - cypress: "npm:13.10.0" + cypress: "npm:14.3.2" cypress-file-upload: "npm:5.0.8" cypress-recurse: "npm:^1.35.3" d3: "npm:7.9.0" @@ -18779,14 +18616,14 @@ __metadata: languageName: node linkType: hard -"http-signature@npm:~1.3.6": - version: 1.3.6 - resolution: "http-signature@npm:1.3.6" +"http-signature@npm:~1.4.0": + version: 1.4.0 + resolution: "http-signature@npm:1.4.0" dependencies: assert-plus: "npm:^1.0.0" jsprim: "npm:^2.0.2" - sshpk: "npm:^1.14.1" - checksum: 10/5f08e0c82174999da97114facb0d0d47e268d60b6fc10f92cb87b99d5ccccd36f79b9508c29dda0b4f4e3a1b2f7bcaf847e68ecd5da2f1fc465fcd1d054b7884 + sshpk: "npm:^1.18.0" + checksum: 10/f9f5eed4ac5db5e1ec6d00652680c7d8b76d553560017e34505c0c22c37abb2e6d22b9268ed4a8542aa9746852a2d64850531091e443393c9c8e0f4fd4174455 languageName: node linkType: hard @@ -19398,7 +19235,7 @@ __metadata: languageName: node linkType: hard -"is-ci@npm:3.0.1, is-ci@npm:^3.0.1": +"is-ci@npm:3.0.1": version: 3.0.1 resolution: "is-ci@npm:3.0.1" dependencies: @@ -25463,15 +25300,6 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.10.4": - version: 6.10.4 - resolution: "qs@npm:6.10.4" - dependencies: - side-channel: "npm:^1.0.4" - checksum: 10/8887a53f63180e0e0291deafef581e550bc3656f2453adc8d3ca34b49c04354d31079962f7faf90ab8f5fd6e3d70ee6645042b27814a757a3a5d5708ae3f58e0 - languageName: node - linkType: hard - "qs@npm:6.13.0": version: 6.13.0 resolution: "qs@npm:6.13.0" @@ -25481,12 +25309,12 @@ __metadata: languageName: node linkType: hard -"qs@npm:^6.11.2, qs@npm:^6.4.0": - version: 6.13.1 - resolution: "qs@npm:6.13.1" +"qs@npm:6.14.0, qs@npm:^6.11.2, qs@npm:^6.4.0": + version: 6.14.0 + resolution: "qs@npm:6.14.0" dependencies: - side-channel: "npm:^1.0.6" - checksum: 10/53cf5fdc5f342a9ffd3968f20c8c61624924cf928d86fff525240620faba8ca5cfd6c3f12718cc755561bfc3dc9721bc8924e38f53d8925b03940f0b8a902212 + side-channel: "npm:^1.1.0" + checksum: 10/a60e49bbd51c935a8a4759e7505677b122e23bf392d6535b8fc31c1e447acba2c901235ecb192764013cd2781723dc1f61978b5fdd93cc31d7043d31cdc01974 languageName: node linkType: hard @@ -27419,20 +27247,13 @@ __metadata: languageName: node linkType: hard -"resolve.exports@npm:2.0.3": +"resolve.exports@npm:2.0.3, resolve.exports@npm:^2.0.0": version: 2.0.3 resolution: "resolve.exports@npm:2.0.3" checksum: 10/536efee0f30a10fac8604e6cdc7844dbc3f4313568d09f06db4f7ed8a5b8aeb8585966fe975083d1f2dfbc87cf5f8bc7ab65a5c23385c14acbb535ca79f8398a languageName: node linkType: hard -"resolve.exports@npm:^2.0.0": - version: 2.0.2 - resolution: "resolve.exports@npm:2.0.2" - checksum: 10/f1cc0b6680f9a7e0345d783e0547f2a5110d8336b3c2a4227231dd007271ffd331fd722df934f017af90bae0373920ca0d4005da6f76cb3176c8ae426370f893 - languageName: node - linkType: hard - "resolve@npm:^1.10.0, resolve@npm:^1.14.2, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.4, resolve@npm:^1.22.8": version: 1.22.8 resolution: "resolve@npm:1.22.8" @@ -27563,7 +27384,7 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:^3.0.0, rimraf@npm:^3.0.2": +"rimraf@npm:^3.0.2": version: 3.0.2 resolution: "rimraf@npm:3.0.2" dependencies: @@ -28085,7 +27906,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.6.3, semver@npm:^7.7.0": +"semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.6.3, semver@npm:^7.7.0, semver@npm:^7.7.1": version: 7.7.1 resolution: "semver@npm:7.7.1" bin: @@ -28402,7 +28223,7 @@ __metadata: languageName: node linkType: hard -"side-channel@npm:^1.0.4, side-channel@npm:^1.0.6, side-channel@npm:^1.1.0": +"side-channel@npm:^1.0.6, side-channel@npm:^1.1.0": version: 1.1.0 resolution: "side-channel@npm:1.1.0" dependencies: @@ -29029,9 +28850,9 @@ __metadata: languageName: node linkType: hard -"sshpk@npm:^1.14.1": - version: 1.17.0 - resolution: "sshpk@npm:1.17.0" +"sshpk@npm:^1.18.0": + version: 1.18.0 + resolution: "sshpk@npm:1.18.0" dependencies: asn1: "npm:~0.2.3" assert-plus: "npm:^1.0.0" @@ -29046,7 +28867,7 @@ __metadata: sshpk-conv: bin/sshpk-conv sshpk-sign: bin/sshpk-sign sshpk-verify: bin/sshpk-verify - checksum: 10/668c2a279a6ce66fd739ce5684e37927dd75427cc020c828a208f85890a4c400705d4ba09f32fa44efca894339dc6931941664f6f6ba36dfa543de6d006cbe9c + checksum: 10/858339d43e3c6b6a848772a66f69442ce74f1a37655d9f35ba9d1f85329499ff0000af9f8ab83dbb39ad24c0c370edabe0be1e39863f70c6cded9924b8458c34 languageName: node linkType: hard @@ -30089,6 +29910,24 @@ __metadata: languageName: node linkType: hard +"tldts-core@npm:^6.1.86": + version: 6.1.86 + resolution: "tldts-core@npm:6.1.86" + checksum: 10/cb5dff9cc15661ac773a2099e98c99a5cb3cebc35909c23cc4261ff7992032c7501995ae995de3574dbbf3431e59c47496534d52f5e96abcb231f0e72144c020 + languageName: node + linkType: hard + +"tldts@npm:^6.1.32": + version: 6.1.86 + resolution: "tldts@npm:6.1.86" + dependencies: + tldts-core: "npm:^6.1.86" + bin: + tldts: bin/cli.js + checksum: 10/f7e66824e44479ccdda55ea556af14ce61c4d27708be403e3f90631defde49f82a580e1ca07187cc7e3b349e257a30c2808a22903f3a0548e136ebb609ccc109 + languageName: node + linkType: hard + "tmp@npm:^0.0.33": version: 0.0.33 resolution: "tmp@npm:0.0.33" @@ -30098,12 +29937,10 @@ __metadata: languageName: node linkType: hard -"tmp@npm:~0.2.1": - version: 0.2.1 - resolution: "tmp@npm:0.2.1" - dependencies: - rimraf: "npm:^3.0.0" - checksum: 10/445148d72df3ce99356bc89a7857a0c5c3b32958697a14e50952c6f7cf0a8016e746ababe9a74c1aa52f04c526661992f14659eba34d3c6701d49ba2f3cf781b +"tmp@npm:~0.2.1, tmp@npm:~0.2.3": + version: 0.2.3 + resolution: "tmp@npm:0.2.3" + checksum: 10/7b13696787f159c9754793a83aa79a24f1522d47b87462ddb57c18ee93ff26c74cbb2b8d9138f571d2e0e765c728fb2739863a672b280528512c6d83d511c6fa languageName: node linkType: hard @@ -30188,7 +30025,7 @@ __metadata: languageName: node linkType: hard -"tough-cookie@npm:^4.1.2, tough-cookie@npm:^4.1.3, tough-cookie@npm:^4.1.4": +"tough-cookie@npm:^4.1.2, tough-cookie@npm:^4.1.4": version: 4.1.4 resolution: "tough-cookie@npm:4.1.4" dependencies: @@ -30200,6 +30037,15 @@ __metadata: languageName: node linkType: hard +"tough-cookie@npm:^5.0.0": + version: 5.1.2 + resolution: "tough-cookie@npm:5.1.2" + dependencies: + tldts: "npm:^6.1.32" + checksum: 10/de430e6e6d34b794137e05b8ac2aa6b74ebbe6cdceb4126f168cf1e76101162a4b2e0e7587c3b70e728bd8654fc39958b2035be7619ee6f08e7257610ba4cd04 + languageName: node + linkType: hard + "tr46@npm:^3.0.0": version: 3.0.0 resolution: "tr46@npm:3.0.0" @@ -30223,6 +30069,15 @@ __metadata: languageName: node linkType: hard +"tree-kill@npm:1.2.2": + version: 1.2.2 + resolution: "tree-kill@npm:1.2.2" + bin: + tree-kill: cli.js + checksum: 10/49117f5f410d19c84b0464d29afb9642c863bc5ba40fcb9a245d474c6d5cc64d1b177a6e6713129eb346b40aebb9d4631d967517f9fbe8251c35b21b13cd96c7 + languageName: node + linkType: hard + "tree-sitter-json@npm:=0.24.8": version: 0.24.8 resolution: "tree-sitter-json@npm:0.24.8" @@ -30562,14 +30417,7 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^4.18.2, type-fest@npm:^4.26.1": - version: 4.26.1 - resolution: "type-fest@npm:4.26.1" - checksum: 10/b82676194f80af228cb852e320d2ea8381c89d667d2e4d9f2bdfc8f254bccc039c7741a90c53617a4de0c9fdca8265ed18eb0888cd628f391c5c381c33a9f94b - languageName: node - linkType: hard - -"type-fest@npm:^4.40.0": +"type-fest@npm:^4.18.2, type-fest@npm:^4.26.1, type-fest@npm:^4.40.0": version: 4.40.0 resolution: "type-fest@npm:4.40.0" checksum: 10/dbca20979d18c6b8c87ca28cd999d9ae6b34e0c54c3a87ac65530a32f7a178d38d3788044a589f47c9fde3f3c81422e7b021ec1455f7242b724a2d9c642ce8b8 From b6b493c8516e57ad5dfb66750b5896c7c35b571d Mon Sep 17 00:00:00 2001 From: Sven Grossmann Date: Fri, 25 Apr 2025 16:05:55 +0200 Subject: [PATCH 128/146] Extension Sidebar: Add feature tracking for opened/closed extensions (#104209) Extension Sidebar: Add feature tracking to track opened/closed extensions --- .../ExtensionSidebar/ExtensionSidebarProvider.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx index df23d0f2898..f39c05766fc 100644 --- a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx +++ b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx @@ -2,7 +2,7 @@ import { createContext, ReactNode, useCallback, useContext, useEffect, useState import { useLocalStorage } from 'react-use'; import { store, type ExtensionInfo } from '@grafana/data'; -import { config, getAppEvents, usePluginLinks, locationService } from '@grafana/runtime'; +import { config, getAppEvents, reportInteraction, usePluginLinks, locationService } from '@grafana/runtime'; import { ExtensionPointPluginMeta, getExtensionPointPluginMeta } from 'app/features/plugins/extensions/utils'; import { OpenExtensionSidebarEvent } from 'app/types/events'; @@ -173,6 +173,19 @@ export const ExtensionSidebarContextProvider = ({ children }: ExtensionSidebarCo // update the stored docked component id when it changes useEffect(() => { + const componentMeta = getComponentMetaFromComponentId(dockedComponentId ?? ''); + const storedComponentId = store.get(EXTENSION_SIDEBAR_DOCKED_LOCAL_STORAGE_KEY); + const storedComponentMeta = getComponentMetaFromComponentId(storedComponentId ?? ''); + const opened = dockedComponentId !== undefined; + // we either want to track opened events, or closed events when we have a previous component + if (opened || storedComponentMeta) { + reportInteraction('grafana_extension_sidebar_changed', { + opened: opened, + componentTitle: (opened ? componentMeta : storedComponentMeta)?.componentTitle, + pluginId: (opened ? componentMeta : storedComponentMeta)?.pluginId, + fromLocalstorage: storedComponentId === dockedComponentId, + }); + } if (dockedComponentId) { store.set(EXTENSION_SIDEBAR_DOCKED_LOCAL_STORAGE_KEY, dockedComponentId); } else { From 01afde24f5efcabe493b71845aa7b2965da15e93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Irene=20Rodr=C3=ADguez?= Date: Fri, 25 Apr 2025 16:15:57 +0200 Subject: [PATCH 129/146] Style fixes in grafanactl docs (#104550) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/sources/observability-as-code/grafana-cli/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/observability-as-code/grafana-cli/_index.md b/docs/sources/observability-as-code/grafana-cli/_index.md index a62d94aecbc..58f9d3edc3d 100644 --- a/docs/sources/observability-as-code/grafana-cli/_index.md +++ b/docs/sources/observability-as-code/grafana-cli/_index.md @@ -35,7 +35,7 @@ cards: title: Manage resources with Grafana CLI title_class: pt-0 lh-1 hero: - description: Grafana CLI (grafanactl) is a command-line tool designed to simplify interaction with Grafana instances. It enables users to authenticate, manage multiple environments, and perform administrative tasks through Grafana’s REST API, all from the terminal. Whether you're automating workflows in CI/CD pipelines or switching between staging and production environments, Grafana CLI provides a flexible and scriptable way to manage your Grafana setup efficiently. + description: Grafana CLI (`grafanactl`) is a command-line tool designed to simplify interaction with Grafana instances. It enables users to authenticate, manage multiple environments, and perform administrative tasks through Grafana’s REST API, all from the terminal. Whether you're automating workflows in CI/CD pipelines or switching between staging and production environments, Grafana CLI provides a flexible and scriptable way to manage your Grafana setup efficiently. height: 110 level: 1 title: Grafana CLI From be15e854342af262adab2e613e2c9b96af4dbbe3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 15:55:42 +0100 Subject: [PATCH 130/146] Update dependency @floating-ui/react to v0.27.8 (#104373) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- packages/grafana-prometheus/package.json | 2 +- packages/grafana-ui/package.json | 2 +- yarn.lock | 14 +++++++------- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 522cce88a0a..7cb59e9dc15 100644 --- a/package.json +++ b/package.json @@ -258,7 +258,7 @@ "@emotion/css": "11.13.5", "@emotion/react": "11.14.0", "@fingerprintjs/fingerprintjs": "^3.4.2", - "@floating-ui/react": "0.27.7", + "@floating-ui/react": "0.27.8", "@formatjs/intl-durationformat": "^0.7.0", "@glideapps/glide-data-grid": "^6.0.0", "@grafana/alerting": "workspace:*", diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index c5d72898f7d..e663f032f93 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -37,7 +37,7 @@ }, "dependencies": { "@emotion/css": "11.13.5", - "@floating-ui/react": "0.27.7", + "@floating-ui/react": "0.27.8", "@grafana/data": "12.1.0-pre", "@grafana/e2e-selectors": "12.1.0-pre", "@grafana/plugin-ui": "0.10.5", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 3b82cdeb4a7..ad17eb046dc 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -65,7 +65,7 @@ "@emotion/css": "11.13.5", "@emotion/react": "11.14.0", "@emotion/serialize": "1.3.3", - "@floating-ui/react": "0.27.7", + "@floating-ui/react": "0.27.8", "@grafana/data": "12.1.0-pre", "@grafana/e2e-selectors": "12.1.0-pre", "@grafana/faro-web-sdk": "^1.13.2", diff --git a/yarn.lock b/yarn.lock index ca6f02473d6..0f09df2d118 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2331,9 +2331,9 @@ __metadata: languageName: node linkType: hard -"@floating-ui/react@npm:0.27.7": - version: 0.27.7 - resolution: "@floating-ui/react@npm:0.27.7" +"@floating-ui/react@npm:0.27.8": + version: 0.27.8 + resolution: "@floating-ui/react@npm:0.27.8" dependencies: "@floating-ui/react-dom": "npm:^2.1.2" "@floating-ui/utils": "npm:^0.2.9" @@ -2341,7 +2341,7 @@ __metadata: peerDependencies: react: ">=17.0.0" react-dom: ">=17.0.0" - checksum: 10/8172bffb125d957e2002961a8db06c72239d371d8ab5fdf7150a7e436cb7d532437c36df5bcfe6acb3f8e705456d6b92c299f547827b89efac49768b5b320231 + checksum: 10/9423e3b7d6298918cb14ad3cfa36ba7b88ef86f18c4c47f7870b367716e918757dc0cf9842ef4995ee784b68aebe725c28c2ae143ecfe16380d3ccfd05cacb43 languageName: node linkType: hard @@ -3356,7 +3356,7 @@ __metadata: resolution: "@grafana/prometheus@workspace:packages/grafana-prometheus" dependencies: "@emotion/css": "npm:11.13.5" - "@floating-ui/react": "npm:0.27.7" + "@floating-ui/react": "npm:0.27.8" "@grafana/data": "npm:12.1.0-pre" "@grafana/e2e-selectors": "npm:12.1.0-pre" "@grafana/plugin-ui": "npm:0.10.5" @@ -3584,7 +3584,7 @@ __metadata: "@emotion/react": "npm:11.14.0" "@emotion/serialize": "npm:1.3.3" "@faker-js/faker": "npm:^9.0.0" - "@floating-ui/react": "npm:0.27.7" + "@floating-ui/react": "npm:0.27.8" "@grafana/data": "npm:12.1.0-pre" "@grafana/e2e-selectors": "npm:12.1.0-pre" "@grafana/faro-web-sdk": "npm:^1.13.2" @@ -17655,7 +17655,7 @@ __metadata: "@emotion/eslint-plugin": "npm:11.12.0" "@emotion/react": "npm:11.14.0" "@fingerprintjs/fingerprintjs": "npm:^3.4.2" - "@floating-ui/react": "npm:0.27.7" + "@floating-ui/react": "npm:0.27.8" "@formatjs/intl-durationformat": "npm:^0.7.0" "@glideapps/glide-data-grid": "npm:^6.0.0" "@grafana/alerting": "workspace:*" From 9f048b51272ef10c4de9236fd85b884c121f1f23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Irene=20Rodr=C3=ADguez?= Date: Fri, 25 Apr 2025 17:46:38 +0200 Subject: [PATCH 131/146] Rename grafana cli to grafana server cli (#104087) --- docs/sources/cli.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/sources/cli.md b/docs/sources/cli.md index eb89e24f04d..301741d644a 100644 --- a/docs/sources/cli.md +++ b/docs/sources/cli.md @@ -1,7 +1,7 @@ --- aliases: - administration/cli/ -description: Guide to using grafana cli +description: Guide to using grafana server cli keywords: - grafana - cli @@ -11,15 +11,15 @@ labels: products: - enterprise - oss -title: Grafana CLI +title: Grafana server CLI weight: 400 --- -# Grafana CLI +# Grafana server CLI -Grafana CLI is a small executable that's bundled with Grafana server. +Grafana server CLI is a small executable that's bundled with Grafana server. You can run it on the same machine Grafana server is running on. -Grafana CLI has `plugins` and `admin` commands, as well as global options. +Grafana server CLI has `plugins` and `admin` commands, as well as global options. To list all commands and options: @@ -27,9 +27,9 @@ To list all commands and options: grafana cli -h ``` -## Run Grafana CLI +## Run Grafana server CLI -To run Grafana CLI, add the path to the Grafana binaries in your `PATH` environment variable. +To run Grafana server CLI, add the path to the Grafana binaries in your `PATH` environment variable. Alternately, if your current directory is the `bin` directory, run `./grafana cli`. Otherwise, you can specify full path to the binary. For example, on Linux `/usr/share/grafana/bin/grafana` and on Windows `C:\Program Files\GrafanaLabs\grafana\bin\grafana.exe`, and run it with `grafana cli`. @@ -41,7 +41,7 @@ If you're on Windows, run Windows PowerShell as Administrator. ## Grafana CLI command syntax -The general syntax for commands in Grafana CLI is: +The general syntax for commands in Grafana server CLI is: ```bash grafana cli [global options] command [command options] [arguments...] @@ -49,11 +49,11 @@ grafana cli [global options] command [command options] [arguments...] ## Global options -Grafana CLI allows you to temporarily override certain Grafana default settings. Except for `--help` and `--version`, most global options are only used by developers. +Grafana server CLI allows you to temporarily override certain Grafana default settings. Except for `--help` and `--version`, most global options are only used by developers. Each global option applies only to the command in which it is used. For example, `--pluginsDir value` does not permanently change where Grafana saves plugins. It only changes it for command in which you apply the option. -### Display Grafana CLI help +### Display Grafana server CLI help `--help` or `-h` displays the help, including default paths and Docker configuration information. @@ -63,9 +63,9 @@ Each global option applies only to the command in which it is used. For example, grafana cli -h ``` -### Display Grafana CLI version +### Display Grafana server CLI version -`--version` or `-v` prints the version of Grafana CLI currently running. +`--version` or `-v` prints the version of Grafana server CLI currently running. **Example:** From 72ea92f275c72359bfd2b1437cdadb9438d4f4d8 Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Fri, 25 Apr 2025 09:50:47 -0600 Subject: [PATCH 132/146] Provisioning: Remove warnings for valid usage (#104555) --- pkg/services/dashboards/service/dashboard_service.go | 2 +- pkg/services/folder/folderimpl/folder.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index 1206e1944cd..9f4dbad79d0 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -751,7 +751,7 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d var userID int64 if id, err := identity.UserIdentifier(dto.User.GetID()); err == nil { userID = id - } else { + } else if !identity.IsServiceIdentity(ctx) { dr.log.Debug("User does not belong to a user or service account namespace, using 0 as user ID", "id", dto.User.GetID()) } diff --git a/pkg/services/folder/folderimpl/folder.go b/pkg/services/folder/folderimpl/folder.go index 389d939be1e..ed286327c02 100644 --- a/pkg/services/folder/folderimpl/folder.go +++ b/pkg/services/folder/folderimpl/folder.go @@ -768,7 +768,7 @@ func (s *Service) CreateLegacy(ctx context.Context, cmd *folder.CreateFolderComm var userID int64 if id, err := identity.UserIdentifier(cmd.SignedInUser.GetID()); err == nil { userID = id - } else { + } else if !identity.IsServiceIdentity(ctx) { s.log.Warn("User does not belong to a user or service account namespace, using 0 as user ID", "id", cmd.SignedInUser.GetID()) } @@ -918,7 +918,7 @@ func (s *Service) legacyUpdate(ctx context.Context, cmd *folder.UpdateFolderComm var userID int64 if id, err := identity.UserIdentifier(cmd.SignedInUser.GetID()); err == nil { userID = id - } else { + } else if !identity.IsServiceIdentity(ctx) { s.log.Warn("User does not belong to a user or service account namespace, using 0 as user ID", "id", cmd.SignedInUser.GetID()) } @@ -1441,7 +1441,7 @@ func (s *Service) buildSaveDashboardCommand(ctx context.Context, dto *dashboards var userID int64 if id, err := identity.UserIdentifier(dto.User.GetID()); err == nil { userID = id - } else { + } else if !identity.IsServiceIdentity(ctx) { s.log.Warn("User does not belong to a user or service account namespace, using 0 as user ID", "id", dto.User.GetID()) } From 24caaa74424bd0863954b06df43318e57136fb9a Mon Sep 17 00:00:00 2001 From: Todd Treece <360020+toddtreece@users.noreply.github.com> Date: Fri, 25 Apr 2025 12:43:29 -0400 Subject: [PATCH 133/146] Dashboards: Set provisioning concurrency limit (#104507) --- pkg/services/dashboards/service/dashboard_service.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index 9f4dbad79d0..e1cf5e22ff7 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -36,6 +36,7 @@ import ( "github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/infra/slugify" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/registry/apis/dashboard/legacysearcher" "github.com/grafana/grafana/pkg/services/accesscontrol" @@ -76,6 +77,7 @@ var ( const ( k8sDashboardKvNamespace = "dashboard-cleanup" k8sDashboardKvLastResourceVersionKey = "last-resource-version" + provisioningConcurrencyLimit = 10 ) type DashboardServiceImpl struct { @@ -525,6 +527,7 @@ func (dr *DashboardServiceImpl) GetProvisionedDashboardData(ctx context.Context, results := []*dashboards.DashboardProvisioning{} var mu sync.Mutex g, ctx := errgroup.WithContext(ctx) + g.SetLimit(provisioningConcurrencyLimit) for _, org := range orgs { func(orgID int64) { g.Go(func() error { @@ -2109,6 +2112,9 @@ type dashboardProvisioningWithUID struct { } func (dr *DashboardServiceImpl) searchProvisionedDashboardsThroughK8s(ctx context.Context, query *dashboards.FindPersistedDashboardsQuery) ([]*dashboardProvisioningWithUID, error) { + ctx, span := tracing.Start(ctx, "searchProvisionedDashboardsThroughK8s") + defer span.End() + if query == nil { return nil, errors.New("query cannot be nil") } @@ -2122,10 +2128,13 @@ func (dr *DashboardServiceImpl) searchProvisionedDashboardsThroughK8s(ctx contex return nil, err } + span.SetAttributes(attribute.Int("hits", len(searchResults.Hits))) + // loop through all hits concurrently to get the repo information (if set due to file provisioning) dashs := make([]*dashboardProvisioningWithUID, 0) var mu sync.Mutex g, ctx := errgroup.WithContext(ctx) + g.SetLimit(provisioningConcurrencyLimit) for _, h := range searchResults.Hits { func(hit dashboardv0.DashboardHit) { g.Go(func() error { From ff7b923d3384796f56cbf88798555171befc5b94 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 18:00:25 +0100 Subject: [PATCH 134/146] Update dependency @formatjs/intl-durationformat to v0.7.4 (#104547) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 64 +++++++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/yarn.lock b/yarn.lock index 0f09df2d118..45f8c9a978f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2376,15 +2376,15 @@ __metadata: languageName: node linkType: hard -"@formatjs/ecma402-abstract@npm:2.3.2": - version: 2.3.2 - resolution: "@formatjs/ecma402-abstract@npm:2.3.2" +"@formatjs/ecma402-abstract@npm:2.3.4": + version: 2.3.4 + resolution: "@formatjs/ecma402-abstract@npm:2.3.4" dependencies: - "@formatjs/fast-memoize": "npm:2.2.6" - "@formatjs/intl-localematcher": "npm:0.5.10" - decimal.js: "npm:10" - tslib: "npm:2" - checksum: 10/db31d3d9b36033ea11ec905638ac0c1d2282f5bf53c9c06ee1d0ffd924f4bf64030702c92b56261756c4998dfa6235462689d8eda82d5913f2d7cf636a9416ae + "@formatjs/fast-memoize": "npm:2.2.7" + "@formatjs/intl-localematcher": "npm:0.6.1" + decimal.js: "npm:^10.4.3" + tslib: "npm:^2.8.0" + checksum: 10/573971ffc291096a4b9fcc80b4708124e89bf2e3ac50e0f78b41eb797e9aa1b842f4dc3665e4467a853c738386821769d9e40408a1d25bc73323a1f057a16cf2 languageName: node linkType: hard @@ -2397,12 +2397,12 @@ __metadata: languageName: node linkType: hard -"@formatjs/fast-memoize@npm:2.2.6": - version: 2.2.6 - resolution: "@formatjs/fast-memoize@npm:2.2.6" +"@formatjs/fast-memoize@npm:2.2.7": + version: 2.2.7 + resolution: "@formatjs/fast-memoize@npm:2.2.7" dependencies: - tslib: "npm:2" - checksum: 10/efa5601dddbd94412ee567d5d067dfd206afa2d08553435f6938e69acba3309b83b9b15021cd30550d5fb93817a53b7691098a11a73f621c2d9318efad49fd76 + tslib: "npm:^2.8.0" + checksum: 10/e7e6efc677d63a13d99a854305db471b69f64cbfebdcb6dbe507dab9aa7eaae482ca5de86f343c856ca0a2c8f251672bd1f37c572ce14af602c0287378097d43 languageName: node linkType: hard @@ -2428,13 +2428,13 @@ __metadata: linkType: hard "@formatjs/intl-durationformat@npm:^0.7.0": - version: 0.7.2 - resolution: "@formatjs/intl-durationformat@npm:0.7.2" + version: 0.7.4 + resolution: "@formatjs/intl-durationformat@npm:0.7.4" dependencies: - "@formatjs/ecma402-abstract": "npm:2.3.2" - "@formatjs/intl-localematcher": "npm:0.5.10" - tslib: "npm:2" - checksum: 10/863784f3ac51779516fa29c87705af6d4f1b32191a2a48172d2ce16a99e385887a5641703a222ca2b1a0d334e1ab2587be5ef65f48c5ae8a16ba55138356a232 + "@formatjs/ecma402-abstract": "npm:2.3.4" + "@formatjs/intl-localematcher": "npm:0.6.1" + tslib: "npm:^2.8.0" + checksum: 10/d62273ecd635475ca91e9b501301f3f396403fa91b584c550734b19b2d194ba1316b27303fed985c1d42ae933d54eb220da6540edfdf376b0d9371ecfd0d4e15 languageName: node linkType: hard @@ -2447,12 +2447,12 @@ __metadata: languageName: node linkType: hard -"@formatjs/intl-localematcher@npm:0.5.10": - version: 0.5.10 - resolution: "@formatjs/intl-localematcher@npm:0.5.10" +"@formatjs/intl-localematcher@npm:0.6.1": + version: 0.6.1 + resolution: "@formatjs/intl-localematcher@npm:0.6.1" dependencies: - tslib: "npm:2" - checksum: 10/119e36974607d5d3586570db93358c1f316c0736b83acc81dce88468435a9519a9e58a5abe6ed3078ffe40d6b4ad4613c6371e2a39cd570c9b6f561372ffb14d + tslib: "npm:^2.8.0" + checksum: 10/c7b3bc8395d18670677f207b2fd107561fff5d6394a9b4273c29e0bea920300ec3a2eefead600ebb7761c04a770cada28f78ac059f84d00520bfb57a9db36998 languageName: node linkType: hard @@ -14465,7 +14465,7 @@ __metadata: languageName: node linkType: hard -"decimal.js@npm:10, decimal.js@npm:^10.4.1": +"decimal.js@npm:^10.4.1, decimal.js@npm:^10.4.3": version: 10.5.0 resolution: "decimal.js@npm:10.5.0" checksum: 10/714d49cf2f2207b268221795ede330e51452b7c451a0c02a770837d2d4faed47d603a729c2aa1d952eb6c4102d999e91c9b952c1aa016db3c5cba9fc8bf4cda2 @@ -30272,13 +30272,6 @@ __metadata: languageName: node linkType: hard -"tslib@npm:2, tslib@npm:2.8.1, tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.4.1, tslib@npm:^2.6.2, tslib@npm:^2.7.0, tslib@npm:^2.8.0": - version: 2.8.1 - resolution: "tslib@npm:2.8.1" - checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7 - languageName: node - linkType: hard - "tslib@npm:2.4.0": version: 2.4.0 resolution: "tslib@npm:2.4.0" @@ -30293,6 +30286,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:2.8.1, tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.4.1, tslib@npm:^2.6.2, tslib@npm:^2.7.0, tslib@npm:^2.8.0": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7 + languageName: node + linkType: hard + "tslib@npm:^1.10.0, tslib@npm:^1.8.1": version: 1.14.1 resolution: "tslib@npm:1.14.1" From 4adebd605804d40d73bcfe1f39622756a54dbfc4 Mon Sep 17 00:00:00 2001 From: Will Assis <35489495+gassiss@users.noreply.github.com> Date: Fri, 25 Apr 2025 14:08:44 -0300 Subject: [PATCH 135/146] unified-storage: setup ring to shard requests (#103783) * Updates the instrumentation_server service to use mux instead of the builtin router, and have it store the router in the module server: this is so we can register the /ring endpoint to check the status of the ring * Create a new Ring service that depends on the instrumentation server and declares it as a dependency for the storage server * Create standalone MemberlistKV service for Ring service to use * Update the storage server Search and GetStats handler to distribute requests if applicable --- pkg/extensions/enterprise_imports.go | 5 - pkg/modules/dependencies.go | 5 +- pkg/server/instrumentation_service.go | 17 +- pkg/server/instrumentation_service_test.go | 7 +- pkg/server/memberlist.go | 53 +++++ pkg/server/module_server.go | 20 +- pkg/server/ring.go | 222 ++++++++++++++++++ pkg/setting/setting.go | 5 + pkg/setting/setting_unified_storage.go | 5 + pkg/storage/unified/apistore/go.mod | 18 ++ pkg/storage/unified/apistore/go.sum | 21 ++ pkg/storage/unified/client.go | 2 +- pkg/storage/unified/resource/go.mod | 27 +++ pkg/storage/unified/resource/go.sum | 167 +++++++++++++ pkg/storage/unified/resource/server.go | 92 +++++++- pkg/storage/unified/sql/server.go | 3 +- pkg/storage/unified/sql/service.go | 6 +- .../unified/sql/test/integration_test.go | 2 +- pkg/tests/testinfra/testinfra.go | 2 +- 19 files changed, 654 insertions(+), 25 deletions(-) create mode 100644 pkg/server/memberlist.go create mode 100644 pkg/server/ring.go diff --git a/pkg/extensions/enterprise_imports.go b/pkg/extensions/enterprise_imports.go index 361121f80d5..7e96faa86ac 100644 --- a/pkg/extensions/enterprise_imports.go +++ b/pkg/extensions/enterprise_imports.go @@ -32,11 +32,6 @@ import ( _ "golang.org/x/time/rate" _ "xorm.io/builder" - _ "github.com/grafana/authlib/authn" - _ "github.com/grafana/authlib/authz" - _ "github.com/grafana/authlib/cache" - _ "github.com/grafana/authlib/grpcutils" - _ "github.com/grafana/authlib/types" _ "github.com/grafana/dskit/backoff" _ "github.com/grafana/dskit/flagext" _ "github.com/grafana/e2e" diff --git a/pkg/modules/dependencies.go b/pkg/modules/dependencies.go index 7645386d823..d4ed8de2177 100644 --- a/pkg/modules/dependencies.go +++ b/pkg/modules/dependencies.go @@ -5,7 +5,9 @@ const ( All string = "all" Core string = "core" + MemberlistKV string = "memberlistkv" GrafanaAPIServer string = "grafana-apiserver" + StorageRing string = "storage-ring" StorageServer string = "storage-server" ZanzanaServer string = "zanzana-server" InstrumentationServer string = "instrumentation-server" @@ -13,8 +15,9 @@ const ( ) var dependencyMap = map[string][]string{ + StorageRing: {InstrumentationServer, MemberlistKV}, GrafanaAPIServer: {InstrumentationServer}, - StorageServer: {InstrumentationServer}, + StorageServer: {InstrumentationServer, StorageRing}, ZanzanaServer: {InstrumentationServer}, Core: {}, All: {Core}, diff --git a/pkg/server/instrumentation_service.go b/pkg/server/instrumentation_service.go index 9a200ec7f38..817064cdd83 100644 --- a/pkg/server/instrumentation_service.go +++ b/pkg/server/instrumentation_service.go @@ -6,6 +6,7 @@ import ( "net/http" "time" + "github.com/gorilla/mux" "github.com/grafana/dskit/services" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/setting" @@ -22,14 +23,14 @@ type instrumentationService struct { promGatherer prometheus.Gatherer } -func NewInstrumentationService(log log.Logger, cfg *setting.Cfg, promGatherer prometheus.Gatherer) (*instrumentationService, error) { - s := &instrumentationService{log: log, cfg: cfg, promGatherer: promGatherer} +func (ms *ModuleServer) initInstrumentationServer() (*instrumentationService, error) { + s := &instrumentationService{log: ms.log, cfg: ms.cfg, promGatherer: ms.promGatherer} + s.httpServ, ms.httpServerRouter = s.newInstrumentationServer() s.BasicService = services.NewBasicService(s.start, s.running, s.stop) return s, nil } func (s *instrumentationService) start(ctx context.Context) error { - s.httpServ = s.newInstrumentationServer(ctx) s.errChan = make(chan error) go func() { s.errChan <- s.httpServ.ListenAndServe() @@ -56,17 +57,17 @@ func (s *instrumentationService) stop(failureReason error) error { return nil } -func (s *instrumentationService) newInstrumentationServer(ctx context.Context) *http.Server { - router := http.NewServeMux() +func (s *instrumentationService) newInstrumentationServer() (*http.Server, *mux.Router) { + router := mux.NewRouter() router.Handle("/metrics", promhttp.HandlerFor(s.promGatherer, promhttp.HandlerOpts{EnableOpenMetrics: true})) + addr := net.JoinHostPort(s.cfg.HTTPAddr, s.cfg.HTTPPort) srv := &http.Server{ // 5s timeout for header reads to avoid Slowloris attacks (https://thetooth.io/blog/slowloris-attack/) ReadHeaderTimeout: 5 * time.Second, - Addr: ":" + s.cfg.HTTPPort, + Addr: addr, Handler: router, - BaseContext: func(_ net.Listener) context.Context { return ctx }, } - return srv + return srv, router } diff --git a/pkg/server/instrumentation_service_test.go b/pkg/server/instrumentation_service_test.go index 1dc8ffa8349..7268d415112 100644 --- a/pkg/server/instrumentation_service_test.go +++ b/pkg/server/instrumentation_service_test.go @@ -18,7 +18,12 @@ import ( func TestRunInstrumentationService(t *testing.T) { cfg := setting.NewCfg() cfg.HTTPPort = "3001" - s, err := NewInstrumentationService(log.New("test-logger"), cfg, prometheus.DefaultGatherer) + ms := ModuleServer{ + log: log.New("test-logger"), + cfg: cfg, + promGatherer: prometheus.DefaultGatherer, + } + s, err := ms.initInstrumentationServer() require.NoError(t, err) ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second) diff --git a/pkg/server/memberlist.go b/pkg/server/memberlist.go new file mode 100644 index 00000000000..b80a54fd2f5 --- /dev/null +++ b/pkg/server/memberlist.go @@ -0,0 +1,53 @@ +package server + +import ( + "github.com/grafana/dskit/dns" + "github.com/grafana/dskit/flagext" + "github.com/grafana/dskit/kv" + "github.com/grafana/dskit/kv/codec" + "github.com/grafana/dskit/kv/memberlist" + "github.com/grafana/dskit/ring" + "github.com/grafana/dskit/services" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/setting" + "github.com/prometheus/client_golang/prometheus" +) + +func (ms *ModuleServer) initMemberlistKV() (services.Service, error) { + logger := log.New("memberlist") + + dnsProviderReg := prometheus.WrapRegistererWithPrefix( + "grafana", + prometheus.WrapRegistererWith( + prometheus.Labels{"component": "memberlist"}, + ms.registerer, + ), + ) + dnsProvider := dns.NewProvider(logger, dnsProviderReg, dns.GolangResolverType) + + KVStore := kv.Config{Store: "memberlist"} + + memberlistKVsvc := memberlist.NewKVInitService(toMemberlistConfig(ms.cfg), logger, dnsProvider, ms.registerer) + KVStore.MemberlistKV = memberlistKVsvc.GetMemberlistKV + + ms.MemberlistKVConfig = KVStore + + return memberlistKVsvc, nil +} + +func toMemberlistConfig(cfg *setting.Cfg) *memberlist.KVConfig { + memberlistKVcfg := &memberlist.KVConfig{} + flagext.DefaultValues(memberlistKVcfg) + memberlistKVcfg.Codecs = []codec.Codec{ + ring.GetCodec(), + } + if cfg.MemberlistBindAddr != "" { + memberlistKVcfg.TCPTransport.BindAddrs = []string{cfg.MemberlistBindAddr} + } + if cfg.MemberlistAdvertiseAddr != "" { + memberlistKVcfg.AdvertiseAddr = cfg.MemberlistAdvertiseAddr + } + memberlistKVcfg.JoinMembers = []string{cfg.MemberlistJoinMember} + + return memberlistKVcfg +} diff --git a/pkg/server/module_server.go b/pkg/server/module_server.go index defea7e9a08..c3774020ca4 100644 --- a/pkg/server/module_server.go +++ b/pkg/server/module_server.go @@ -9,6 +9,8 @@ import ( "strconv" "sync" + "github.com/gorilla/mux" + "github.com/grafana/dskit/kv" "github.com/prometheus/client_golang/prometheus" "github.com/grafana/dskit/services" @@ -32,10 +34,11 @@ func NewModule(opts Options, cfg *setting.Cfg, storageMetrics *resource.StorageMetrics, indexMetrics *resource.BleveIndexMetrics, + reg prometheus.Registerer, promGatherer prometheus.Gatherer, license licensing.Licensing, ) (*ModuleServer, error) { - s, err := newModuleServer(opts, apiOpts, features, cfg, storageMetrics, indexMetrics, promGatherer, license) + s, err := newModuleServer(opts, apiOpts, features, cfg, storageMetrics, indexMetrics, reg, promGatherer, license) if err != nil { return nil, err } @@ -47,7 +50,7 @@ func NewModule(opts Options, return s, nil } -func newModuleServer(opts Options, apiOpts api.ServerOptions, features featuremgmt.FeatureToggles, cfg *setting.Cfg, storageMetrics *resource.StorageMetrics, indexMetrics *resource.BleveIndexMetrics, promGatherer prometheus.Gatherer, license licensing.Licensing) (*ModuleServer, error) { +func newModuleServer(opts Options, apiOpts api.ServerOptions, features featuremgmt.FeatureToggles, cfg *setting.Cfg, storageMetrics *resource.StorageMetrics, indexMetrics *resource.BleveIndexMetrics, reg prometheus.Registerer, promGatherer prometheus.Gatherer, license licensing.Licensing) (*ModuleServer, error) { rootCtx, shutdownFn := context.WithCancel(context.Background()) s := &ModuleServer{ @@ -66,6 +69,7 @@ func newModuleServer(opts Options, apiOpts api.ServerOptions, features featuremg storageMetrics: storageMetrics, indexMetrics: indexMetrics, promGatherer: promGatherer, + registerer: reg, license: license, } @@ -98,6 +102,11 @@ type ModuleServer struct { buildBranch string promGatherer prometheus.Gatherer + registerer prometheus.Registerer + + MemberlistKVConfig kv.Config + httpServerRouter *mux.Router + distributor *resource.Distributor } // init initializes the server and its services. @@ -136,9 +145,12 @@ func (s *ModuleServer) Run() error { if m.IsModuleEnabled(modules.All) || m.IsModuleEnabled(modules.Core) || m.IsModuleEnabled(modules.FrontendServer) { return services.NewBasicService(nil, nil, nil).WithName(modules.InstrumentationServer), nil } - return NewInstrumentationService(s.log, s.cfg, s.promGatherer) + return s.initInstrumentationServer() }) + m.RegisterModule(modules.MemberlistKV, s.initMemberlistKV) + m.RegisterModule(modules.StorageRing, s.initRing) + m.RegisterModule(modules.Core, func() (services.Service, error) { return NewService(s.cfg, s.opts, s.apiOpts) }) @@ -157,7 +169,7 @@ func (s *ModuleServer) Run() error { if err != nil { return nil, err } - return sql.ProvideUnifiedStorageGrpcService(s.cfg, s.features, nil, s.log, nil, docBuilders, s.storageMetrics, s.indexMetrics) + return sql.ProvideUnifiedStorageGrpcService(s.cfg, s.features, nil, s.log, nil, docBuilders, s.storageMetrics, s.indexMetrics, s.distributor) }) m.RegisterModule(modules.ZanzanaServer, func() (services.Service, error) { diff --git a/pkg/server/ring.go b/pkg/server/ring.go new file mode 100644 index 00000000000..a76e6fc3ef2 --- /dev/null +++ b/pkg/server/ring.go @@ -0,0 +1,222 @@ +package server + +import ( + "context" + "fmt" + "net" + "os" + "strconv" + "time" + + "github.com/grafana/dskit/flagext" + "github.com/grafana/dskit/grpcclient" + "github.com/grafana/dskit/kv" + "github.com/grafana/dskit/netutil" + "github.com/grafana/dskit/ring" + ringclient "github.com/grafana/dskit/ring/client" + "github.com/grafana/dskit/services" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/storage/unified/resource" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "google.golang.org/grpc" + "google.golang.org/grpc/health/grpc_health_v1" +) + +const ringKey = "storage-ring" +const ringName = "unified_storage" +const numTokens = 128 +const heartbeatTimeout = time.Minute + +var metricsPrefix = ringName + "_" + +func (ms *ModuleServer) initRing() (services.Service, error) { + if !ms.cfg.EnableSharding { + return nil, nil + } + + logger := log.New("resource-server-ring") + reg := prometheus.WrapRegistererWithPrefix(metricsPrefix, ms.registerer) + + grpcclientcfg := &grpcclient.Config{} + flagext.DefaultValues(grpcclientcfg) + pool := newClientPool(*grpcclientcfg, logger, reg) + + ringStore, err := kv.NewClient( + ms.MemberlistKVConfig, + ring.GetCodec(), + kv.RegistererWithKVName(reg, ringName), + logger, + ) + if err != nil { + return nil, fmt.Errorf("failed to create KV store client: %s", err) + } + + lifecyclerCfg, err := toLifecyclerConfig(ms.cfg, logger) + if err != nil { + return nil, fmt.Errorf("failed to initialize storage-ring lifecycler config: %s", err) + } + + // Define lifecycler delegates in reverse order (last to be called defined first because they're + // chained via "next delegate"). + delegate := ring.BasicLifecyclerDelegate(ring.NewInstanceRegisterDelegate(ring.JOINING, numTokens)) + delegate = ring.NewLeaveOnStoppingDelegate(delegate, logger) + delegate = ring.NewAutoForgetDelegate(heartbeatTimeout*2, delegate, logger) + + lifecycler, err := ring.NewBasicLifecycler( + lifecyclerCfg, + ringName, + ringKey, + ringStore, + delegate, + logger, + reg, + ) + if err != nil { + return nil, fmt.Errorf("failed to initialize storage-ring lifecycler: %s", err) + } + + storageRing, err := ring.NewWithStoreClientAndStrategy( + toRingConfig(ms.cfg, ms.MemberlistKVConfig), + ringName, + ringKey, + ringStore, + ring.NewIgnoreUnhealthyInstancesReplicationStrategy(), + reg, + logger, + ) + if err != nil { + return nil, fmt.Errorf("failed to initialize storage-ring ring: %s", err) + } + + startFn := func(ctx context.Context) error { + err = storageRing.StartAsync(ctx) + if err != nil { + return fmt.Errorf("failed to start the ring: %s", err) + } + err = lifecycler.StartAsync(ctx) + if err != nil { + return fmt.Errorf("failed to start the lifecycler: %s", err) + } + err = pool.StartAsync(ctx) + if err != nil { + return fmt.Errorf("failed to start the ring client pool: %s", err) + } + + logger.Info("waiting until resource server is JOINING in the ring") + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + if err := ring.WaitInstanceState(ctx, storageRing, lifecycler.GetInstanceID(), ring.JOINING); err != nil { + return fmt.Errorf("error switching to JOINING in the ring: %s", err) + } + logger.Info("resource server is JOINING in the ring") + + if err := lifecycler.ChangeState(ctx, ring.ACTIVE); err != nil { + return fmt.Errorf("error switching to ACTIVE in the ring: %s", err) + } + logger.Info("resource server is ACTIVE in the ring") + + return nil + } + + ms.distributor = &resource.Distributor{ + ClientPool: pool, + Ring: storageRing, + Lifecycler: lifecycler, + } + + ms.httpServerRouter.Path("/ring").Methods("GET", "POST").Handler(storageRing) + + svc := services.NewIdleService(startFn, nil) + + return svc, nil +} + +func toLifecyclerConfig(cfg *setting.Cfg, logger log.Logger) (ring.BasicLifecyclerConfig, error) { + instanceAddr, err := ring.GetInstanceAddr(cfg.MemberlistBindAddr, netutil.PrivateNetworkInterfacesWithFallback([]string{"eth0", "en0"}, logger), logger, true) + if err != nil { + return ring.BasicLifecyclerConfig{}, err + } + + instanceId := cfg.InstanceID + if instanceId == "" { + hostname, err := os.Hostname() + if err != nil { + return ring.BasicLifecyclerConfig{}, err + } + + instanceId = hostname + } + + _, grpcPortStr, err := net.SplitHostPort(cfg.GRPCServer.Address) + if err != nil { + return ring.BasicLifecyclerConfig{}, fmt.Errorf("could not get grpc port from grpc server address: %s", err) + } + + grpcPort, err := strconv.Atoi(grpcPortStr) + if err != nil { + return ring.BasicLifecyclerConfig{}, fmt.Errorf("error converting grpc address port to int: %s", err) + } + + return ring.BasicLifecyclerConfig{ + Addr: fmt.Sprintf("%s:%d", instanceAddr, grpcPort), + ID: instanceId, + HeartbeatPeriod: 15 * time.Second, + HeartbeatTimeout: heartbeatTimeout, + TokensObservePeriod: 0, + NumTokens: numTokens, + }, nil +} + +func toRingConfig(cfg *setting.Cfg, KVStore kv.Config) ring.Config { + rc := ring.Config{} + flagext.DefaultValues(&rc) + + rc.KVStore = KVStore + rc.HeartbeatTimeout = heartbeatTimeout + + rc.ReplicationFactor = 1 + + return rc +} + +func newClientPool(clientCfg grpcclient.Config, log log.Logger, reg prometheus.Registerer) *ringclient.Pool { + poolCfg := ringclient.PoolConfig{ + CheckInterval: 10 * time.Second, + HealthCheckEnabled: true, + HealthCheckTimeout: 10 * time.Second, + } + clientsCount := promauto.With(reg).NewGauge(prometheus.GaugeOpts{ + Name: "resource_server_clients", + Help: "The current number of resource server clients in the pool.", + }) + factoryRequestDuration := promauto.With(reg).NewHistogramVec(prometheus.HistogramOpts{ + Name: "resource_server_client_request_duration_seconds", + Help: "Time spent executing requests to resource server.", + Buckets: prometheus.ExponentialBuckets(0.008, 4, 7), + }, []string{"operation", "status_code"}) + + factory := ringclient.PoolInstFunc(func(inst ring.InstanceDesc) (ringclient.PoolClient, error) { + opts, err := clientCfg.DialOption(grpcclient.Instrument(factoryRequestDuration)) + if err != nil { + return nil, err + } + + conn, err := grpc.NewClient(inst.Addr, opts...) + if err != nil { + return nil, fmt.Errorf("failed to dial resource server %s %s: %s", inst.Id, inst.Addr, err) + } + + // TODO only use this if FlagAppPlatformGrpcClientAuth is not enabled + client := resource.NewLegacyResourceClient(conn) + + return &resource.RingClient{ + Client: client, + HealthClient: grpc_health_v1.NewHealthClient(conn), + Conn: conn, + }, nil + }) + + return ringclient.NewPool(ringName, poolCfg, nil, factory, clientsCount, log) +} diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index f0cd2815fd6..bafdcc1b2dc 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -547,6 +547,11 @@ type Cfg struct { IndexMaxBatchSize int IndexFileThreshold int IndexMinCount int + EnableSharding bool + MemberlistBindAddr string + MemberlistAdvertiseAddr string + MemberlistJoinMember string + InstanceID string SprinklesApiServer string SprinklesApiServerPageLimit int CACertPath string diff --git a/pkg/setting/setting_unified_storage.go b/pkg/setting/setting_unified_storage.go index c805f7a330f..45ddfb4b3f9 100644 --- a/pkg/setting/setting_unified_storage.go +++ b/pkg/setting/setting_unified_storage.go @@ -54,6 +54,11 @@ func (cfg *Cfg) setUnifiedStorageConfig() { cfg.IndexPath = section.Key("index_path").String() cfg.IndexWorkers = section.Key("index_workers").MustInt(10) cfg.IndexMaxBatchSize = section.Key("index_max_batch_size").MustInt(100) + cfg.EnableSharding = section.Key("enable_sharding").MustBool(false) + cfg.MemberlistBindAddr = section.Key("memberlist_bind_addr").String() + cfg.MemberlistAdvertiseAddr = section.Key("memberlist_advertise_addr").String() + cfg.MemberlistJoinMember = section.Key("memberlist_join_member").String() + cfg.InstanceID = section.Key("instance_id").String() cfg.IndexFileThreshold = section.Key("index_file_threshold").MustInt(10) cfg.IndexMinCount = section.Key("index_min_count").MustInt(1) cfg.SprinklesApiServer = section.Key("sprinkles_api_server").String() diff --git a/pkg/storage/unified/apistore/go.mod b/pkg/storage/unified/apistore/go.mod index 9b10cca536e..e6dcc828956 100644 --- a/pkg/storage/unified/apistore/go.mod +++ b/pkg/storage/unified/apistore/go.mod @@ -49,7 +49,9 @@ require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 // indirect + github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect github.com/apache/arrow-go/v18 v18.2.0 // indirect + github.com/armon/go-metrics v0.4.1 // indirect github.com/aws/aws-sdk-go v1.55.6 // indirect github.com/aws/aws-sdk-go-v2 v1.36.1 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect @@ -107,6 +109,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/gnostic-models v0.6.9 // indirect @@ -130,9 +133,20 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.1 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/hashicorp/consul/api v1.30.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-msgpack v1.1.5 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.6.3 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.6 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hashicorp/memberlist v0.5.0 // indirect + github.com/hashicorp/serf v0.10.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jhump/protoreflect v1.15.1 // indirect @@ -149,6 +163,9 @@ require ( github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/miekg/dns v1.1.62 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -172,6 +189,7 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/spf13/cobra v1.9.1 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/stretchr/objx v0.5.2 // indirect diff --git a/pkg/storage/unified/apistore/go.sum b/pkg/storage/unified/apistore/go.sum index 61fae46133a..34fe320d513 100644 --- a/pkg/storage/unified/apistore/go.sum +++ b/pkg/storage/unified/apistore/go.sum @@ -58,12 +58,14 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapp github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0/go.mod h1:wRbFgBQUVm1YXrvWKofAEmq9HNJTDphbAaJSSX01KUI= github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/apache/arrow-go/v18 v18.2.0 h1:QhWqpgZMKfWOniGPhbUxrHohWnooGURqL2R2Gg4SO1Q= github.com/apache/arrow-go/v18 v18.2.0/go.mod h1:Ic/01WSwGJWRrdAZcxjBZ5hbApNJ28K96jGYaxzzGUc= github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE= github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk= github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go-v2 v1.36.1 h1:iTDl5U6oAhkNPba0e1t1hrwAo02ZMqbrGq4k5JBWM5E= @@ -309,12 +311,26 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/hashicorp/consul/api v1.30.0 h1:ArHVMMILb1nQv8vZSGIwwQd2gtc+oSQZ6CalyiyH2XQ= +github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-msgpack v1.1.5 h1:9byZdVjKTe5mce63pRVNP1L7UAmdHOTEMGehn6KvJWs= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg= github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= +github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -379,11 +395,14 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -413,6 +432,7 @@ github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQ github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= @@ -455,6 +475,7 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= diff --git a/pkg/storage/unified/client.go b/pkg/storage/unified/client.go index d5b785b542f..2df1f962f3c 100644 --- a/pkg/storage/unified/client.go +++ b/pkg/storage/unified/client.go @@ -158,7 +158,7 @@ func newClient(opts options.StorageOptions, if err != nil { return nil, err } - server, err := sql.NewResourceServer(db, cfg, tracer, reg, authzc, searchOptions, storageMetrics, indexMetrics, features) + server, err := sql.NewResourceServer(db, cfg, tracer, reg, authzc, searchOptions, storageMetrics, indexMetrics, features, nil) if err != nil { return nil, err } diff --git a/pkg/storage/unified/resource/go.mod b/pkg/storage/unified/resource/go.mod index 2073290530d..8b12c1164c9 100644 --- a/pkg/storage/unified/resource/go.mod +++ b/pkg/storage/unified/resource/go.mod @@ -57,7 +57,9 @@ require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 // indirect github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect + github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect github.com/apache/arrow-go/v18 v18.2.0 // indirect + github.com/armon/go-metrics v0.4.1 // indirect github.com/aws/aws-sdk-go v1.55.6 // indirect github.com/aws/aws-sdk-go-v2 v1.36.1 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect @@ -86,6 +88,8 @@ require ( github.com/chromedp/cdproto v0.0.0-20240810084448-b931b754e476 // indirect github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/elazarl/goproxy v1.7.2 // indirect @@ -110,6 +114,8 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/btree v1.1.3 // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/gnostic-models v0.6.9 // indirect github.com/google/go-cmp v0.7.0 // indirect @@ -123,8 +129,20 @@ require ( github.com/grafana/otel-profiling-go v0.5.1 // indirect github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect + github.com/hashicorp/consul/api v1.30.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-msgpack v1.1.5 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.6.3 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.6 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/hashicorp/memberlist v0.5.0 // indirect + github.com/hashicorp/serf v0.10.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/jhump/protoreflect v1.15.1 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -141,6 +159,9 @@ require ( github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/miekg/dns v1.1.62 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -164,6 +185,7 @@ require ( github.com/redis/go-redis/v9 v9.7.3 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect github.com/uber/jaeger-lib v2.4.1+incompatible // indirect @@ -173,6 +195,9 @@ require ( github.com/urfave/cli v1.22.16 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect + go.etcd.io/etcd/api/v3 v3.5.16 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.16 // indirect + go.etcd.io/etcd/client/v3 v3.5.16 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect @@ -188,6 +213,8 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.37.0 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/mod v0.24.0 // indirect diff --git a/pkg/storage/unified/resource/go.sum b/pkg/storage/unified/resource/go.sum index acaf9f37dbc..bc64d461361 100644 --- a/pkg/storage/unified/resource/go.sum +++ b/pkg/storage/unified/resource/go.sum @@ -51,6 +51,7 @@ github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2 github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 h1:o90wcURuxekmXrtxmYWTyNla0+ZEHhud6DI1ZTxd1vI= @@ -62,12 +63,24 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapp github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/apache/arrow-go/v18 v18.2.0 h1:QhWqpgZMKfWOniGPhbUxrHohWnooGURqL2R2Gg4SO1Q= github.com/apache/arrow-go/v18 v18.2.0/go.mod h1:Ic/01WSwGJWRrdAZcxjBZ5hbApNJ28K96jGYaxzzGUc= github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE= github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk= github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go-v2 v1.36.1 h1:iTDl5U6oAhkNPba0e1t1hrwAo02ZMqbrGq4k5JBWM5E= @@ -108,19 +121,25 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.33.12 h1:fqg6c1KVrc3SYWma/egWue5rKI4 github.com/aws/aws-sdk-go-v2/service/sts v1.33.12/go.mod h1:7Yn+p66q/jt38qMoVfNvjbm3D89mGBnkwDcijgtih8w= github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/chromedp/cdproto v0.0.0-20240810084448-b931b754e476 h1:VnjHsRXCRti7Av7E+j4DCha3kf68echfDzQ+wD11SBU= github.com/chromedp/cdproto v0.0.0-20240810084448-b931b754e476/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -128,6 +147,10 @@ github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 h1:boJj011Hh+874zpIySe github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= @@ -158,6 +181,8 @@ github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJP github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= @@ -176,8 +201,12 @@ github.com/getkin/kin-openapi v0.131.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaE github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -194,13 +223,16 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/status v1.1.1 h1:DuHXlSFHNKqTQ+/ACf5Vs6r4X/dH2EgIzR9Vr+H65kg= @@ -214,6 +246,7 @@ github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8J github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -228,6 +261,9 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= @@ -291,12 +327,54 @@ github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.1 h1:KcFzXwzM/kGhIRHvc8jdix github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.1/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/hashicorp/consul/api v1.30.0 h1:ArHVMMILb1nQv8vZSGIwwQd2gtc+oSQZ6CalyiyH2XQ= +github.com/hashicorp/consul/api v1.30.0/go.mod h1:B2uGchvaXVW2JhFoS8nqTxMD5PBykr4ebY4JWHTTeLM= +github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg= +github.com/hashicorp/consul/sdk v0.16.1/go.mod h1:fSXvwxB2hmh1FMZCNl6PwX0Q/1wdWtHJcZ7Ea5tns0s= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v1.1.5 h1:9byZdVjKTe5mce63pRVNP1L7UAmdHOTEMGehn6KvJWs= +github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YNK9NfGLXJ69/4= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg= github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I= +github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= +github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= +github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= +github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= @@ -311,6 +389,8 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 h1:SwcnSwBR7X/5EHJQlXBockkJVIMRVt5yKaesBPMtyZQ= @@ -318,6 +398,7 @@ github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6/go.mod h1:W github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= @@ -329,6 +410,8 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -346,10 +429,16 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -357,23 +446,37 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= +github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= @@ -389,6 +492,9 @@ github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQ github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= @@ -399,6 +505,8 @@ github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= @@ -406,13 +514,26 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d h1:HWfigq7lB31IeJL8iy7jkUmU/PG1Sr8jVGhS749dbUA= @@ -427,7 +548,12 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= @@ -436,11 +562,14 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9 github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -451,6 +580,7 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= @@ -476,6 +606,12 @@ github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +go.etcd.io/etcd/api/v3 v3.5.16 h1:WvmyJVbjWqK4R1E+B12RRHz3bRGy9XVfh++MgbN+6n0= +go.etcd.io/etcd/api/v3 v3.5.16/go.mod h1:1P4SlIP/VwkDmGo3OlOD7faPeP8KDIFhqvciH5EfN28= +go.etcd.io/etcd/client/pkg/v3 v3.5.16 h1:ZgY48uH6UvB+/7R9Yf4x574uCO3jIx0TRDyetSfId3Q= +go.etcd.io/etcd/client/pkg/v3 v3.5.16/go.mod h1:V8acl8pcEK0Y2g19YlOV9m9ssUe6MgiDSobSoaBAM0E= +go.etcd.io/etcd/client/v3 v3.5.16 h1:sSmVYOAHeC9doqi0gv7v86oY/BTld0SEFGaxsU9eRhE= +go.etcd.io/etcd/client/v3 v3.5.16/go.mod h1:X+rExSGkyqxvu276cr2OwPLBaeqFu1cIl4vmRjAD/50= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= @@ -518,10 +654,16 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= gocloud.dev v0.40.0 h1:f8LgP+4WDqOG/RXoUcyLpeIAGOcAbZrZbDQCUee10ng= gocloud.dev v0.40.0/go.mod h1:drz+VyYNBvrMTW0KZiBAYEdl8lbNZx+OQ7oQvdrFmSQ= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -556,15 +698,19 @@ golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= @@ -577,31 +723,45 @@ golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -622,6 +782,7 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= @@ -637,7 +798,9 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190424220101-1e8e1cfdf96b/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -696,6 +859,7 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -705,7 +869,10 @@ gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBl gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/pkg/storage/unified/resource/server.go b/pkg/storage/unified/resource/server.go index 64b5a20a22e..b31d90558d7 100644 --- a/pkg/storage/unified/resource/server.go +++ b/pkg/storage/unified/resource/server.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "hash/fnv" "log/slog" "net/http" "sync" @@ -13,12 +14,16 @@ import ( "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" + "google.golang.org/grpc" + "google.golang.org/grpc/health/grpc_health_v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" claims "github.com/grafana/authlib/types" - + "github.com/grafana/dskit/ring" + ringclient "github.com/grafana/dskit/ring/client" + userutils "github.com/grafana/dskit/user" "github.com/grafana/grafana/pkg/apimachinery/utils" ) @@ -190,6 +195,8 @@ type ResourceServerOptions struct { storageMetrics *StorageMetrics IndexMetrics *BleveIndexMetrics + + Distributor *Distributor } func NewResourceServer(opts ResourceServerOptions) (ResourceServer, error) { @@ -255,6 +262,12 @@ func NewResourceServer(opts ResourceServerOptions) (ResourceServer, error) { cancel: cancel, storageMetrics: opts.storageMetrics, indexMetrics: opts.IndexMetrics, + reg: opts.Reg, + } + + if opts.Distributor != nil { + s.shardingEnabled = true + s.distributor = *opts.Distributor } if opts.Search.Resources != nil { @@ -299,6 +312,34 @@ type server struct { // init checking once sync.Once initErr error + + shardingEnabled bool + distributor Distributor + reg prometheus.Registerer +} + +type Distributor struct { + ClientPool *ringclient.Pool + Ring *ring.Ring + Lifecycler *ring.BasicLifecycler +} + +type RingClient struct { + Client ResourceClient + grpc_health_v1.HealthClient + Conn *grpc.ClientConn +} + +func (c *RingClient) Close() error { + return c.Conn.Close() +} + +func (c *RingClient) String() string { + return c.RemoteAddress() +} + +func (c *RingClient) RemoteAddress() string { + return c.Conn.Target() } // Init implements ResourceServer. @@ -329,6 +370,39 @@ func (s *server) Init(ctx context.Context) error { return s.initErr } +var ringOp = ring.NewOp([]ring.InstanceState{ring.ACTIVE}, func(s ring.InstanceState) bool { + return s != ring.ACTIVE +}) + +func (s *server) getClientToDistributeRequest(namespace string) *RingClient { + ringHasher := fnv.New32a() + _, err := ringHasher.Write([]byte(namespace)) + if err != nil { + s.log.Error("Error hashing namespace. Will not distribute request", "err", err) + return nil + } + + rs, err := s.distributor.Ring.Get(ringHasher.Sum32(), ringOp, nil, nil, nil) + + if err != nil { + s.log.Error("Error getting replication set. Will not distribute request", "err", err) + return nil + } + + if rs.Instances[0].Id != s.distributor.Lifecycler.GetInstanceID() { + s.log.Info("distributing request", "instanceId", rs.Instances[0].Id) + + ins, err := s.distributor.ClientPool.GetClientForInstance(rs.Instances[0]) + if err != nil { + s.log.Error("Error getting client. Will not distribute request", "err", err) + return nil + } + return ins.(*RingClient) + } + + return nil +} + func (s *server) Stop(ctx context.Context) error { s.initErr = fmt.Errorf("service is stopping") @@ -1029,6 +1103,14 @@ func (s *server) Search(ctx context.Context, req *ResourceSearchRequest) (*Resou if s.search == nil { return nil, fmt.Errorf("search index not configured") } + + if s.shardingEnabled { + client := s.getClientToDistributeRequest(req.Options.Key.Namespace) + if client != nil { + return client.Client.Search(userutils.InjectOrgID(ctx, "1"), req) + } + } + return s.search.Search(ctx, req) } @@ -1037,6 +1119,14 @@ func (s *server) GetStats(ctx context.Context, req *ResourceStatsRequest) (*Reso if err := s.Init(ctx); err != nil { return nil, err } + + if s.shardingEnabled { + client := s.getClientToDistributeRequest(req.Namespace) + if client != nil { + return client.Client.GetStats(userutils.InjectOrgID(ctx, "1"), req) + } + } + if s.search == nil { // If the backend implements "GetStats", we can use it srv, ok := s.backend.(ResourceIndexServer) diff --git a/pkg/storage/unified/sql/server.go b/pkg/storage/unified/sql/server.go index e5e1e845a9f..31e84f5a048 100644 --- a/pkg/storage/unified/sql/server.go +++ b/pkg/storage/unified/sql/server.go @@ -21,7 +21,7 @@ import ( func NewResourceServer(db infraDB.DB, cfg *setting.Cfg, tracer trace.Tracer, reg prometheus.Registerer, ac types.AccessClient, searchOptions resource.SearchOptions, storageMetrics *resource.StorageMetrics, - indexMetrics *resource.BleveIndexMetrics, features featuremgmt.FeatureToggles) (resource.ResourceServer, error) { + indexMetrics *resource.BleveIndexMetrics, features featuremgmt.FeatureToggles, distributor *resource.Distributor) (resource.ResourceServer, error) { apiserverCfg := cfg.SectionWithEnvOverrides("grafana-apiserver") opts := resource.ResourceServerOptions{ Tracer: tracer, @@ -68,6 +68,7 @@ func NewResourceServer(db infraDB.DB, cfg *setting.Cfg, opts.Lifecycle = store opts.Search = searchOptions opts.IndexMetrics = indexMetrics + opts.Distributor = distributor rs, err := resource.NewResourceServer(opts) if err != nil { diff --git a/pkg/storage/unified/sql/service.go b/pkg/storage/unified/sql/service.go index 1c25af788c0..764fa046f99 100644 --- a/pkg/storage/unified/sql/service.go +++ b/pkg/storage/unified/sql/service.go @@ -60,6 +60,8 @@ type service struct { indexMetrics *resource.BleveIndexMetrics docBuilders resource.DocumentBuilderSupplier + + distributor *resource.Distributor } func ProvideUnifiedStorageGrpcService( @@ -71,6 +73,7 @@ func ProvideUnifiedStorageGrpcService( docBuilders resource.DocumentBuilderSupplier, storageMetrics *resource.StorageMetrics, indexMetrics *resource.BleveIndexMetrics, + distributor *resource.Distributor, ) (UnifiedStorageGrpcService, error) { tracer := otel.Tracer("unified-storage") @@ -98,6 +101,7 @@ func ProvideUnifiedStorageGrpcService( docBuilders: docBuilders, storageMetrics: storageMetrics, indexMetrics: indexMetrics, + distributor: distributor, } // This will be used when running as a dskit service @@ -117,7 +121,7 @@ func (s *service) start(ctx context.Context) error { return err } - server, err := NewResourceServer(s.db, s.cfg, s.tracing, s.reg, authzClient, searchOptions, s.storageMetrics, s.indexMetrics, s.features) + server, err := NewResourceServer(s.db, s.cfg, s.tracing, s.reg, authzClient, searchOptions, s.storageMetrics, s.indexMetrics, s.features, s.distributor) if err != nil { return err } diff --git a/pkg/storage/unified/sql/test/integration_test.go b/pkg/storage/unified/sql/test/integration_test.go index deb31b10fd9..a9c2f3a82d4 100644 --- a/pkg/storage/unified/sql/test/integration_test.go +++ b/pkg/storage/unified/sql/test/integration_test.go @@ -113,7 +113,7 @@ func TestClientServer(t *testing.T) { features := featuremgmt.WithFeatures() - svc, err := sql.ProvideUnifiedStorageGrpcService(cfg, features, dbstore, nil, prometheus.NewPedanticRegistry(), nil, nil, nil) + svc, err := sql.ProvideUnifiedStorageGrpcService(cfg, features, dbstore, nil, prometheus.NewPedanticRegistry(), nil, nil, nil, nil) require.NoError(t, err) var client resource.ResourceStoreClient diff --git a/pkg/tests/testinfra/testinfra.go b/pkg/tests/testinfra/testinfra.go index 12fd9fc6ca5..468d4e87968 100644 --- a/pkg/tests/testinfra/testinfra.go +++ b/pkg/tests/testinfra/testinfra.go @@ -104,7 +104,7 @@ func StartGrafanaEnv(t *testing.T, grafDir, cfgPath string) (string, *server.Tes var storage sql.UnifiedStorageGrpcService if runstore { storage, err = sql.ProvideUnifiedStorageGrpcService(env.Cfg, env.FeatureToggles, env.SQLStore, - env.Cfg.Logger, prometheus.NewPedanticRegistry(), nil, nil, nil) + env.Cfg.Logger, prometheus.NewPedanticRegistry(), nil, nil, nil, nil) require.NoError(t, err) ctx := context.Background() err = storage.StartAsync(ctx) From a2a13763a4b33d271c185aae19a3357103c30162 Mon Sep 17 00:00:00 2001 From: Kevin Minehart <5140827+kminehart@users.noreply.github.com> Date: Fri, 25 Apr 2025 12:18:20 -0500 Subject: [PATCH 136/146] CI: Remove bad caching from integration tests (#104546) --- .github/workflows/pr-test-integration.yml | 39 ----------------------- 1 file changed, 39 deletions(-) diff --git a/.github/workflows/pr-test-integration.yml b/.github/workflows/pr-test-integration.yml index a0acad9d0de..518e1150531 100644 --- a/.github/workflows/pr-test-integration.yml +++ b/.github/workflows/pr-test-integration.yml @@ -23,22 +23,9 @@ jobs: with: go-version-file: go.mod cache: true - - name: Restore GOCACHE - uses: actions/cache/restore@v4 - with: - key: go-test-cache-${{ github.ref_name }}-sqlite - restore-keys: | - go-test-cache-${{ github.base_ref }}-sqlite - go-test-cache-main-sqlite - path: /home/runner/.cache/go-build - run: | make gen-go go test -tags=sqlite -timeout=5m -run '^TestIntegration' $(find ./pkg -type f -name '*_test.go' -exec grep -l '^func TestIntegration' '{}' '+' | grep -o '\(.*\)/' | sort -u) - - name: Save GOCACHE - uses: actions/cache/save@v4 - with: - key: go-test-cache-${{ github.ref_name }}-sqlite - path: /home/runner/.cache/go-build mysql: name: MySQL runs-on: ubuntu-latest-8-cores @@ -64,24 +51,11 @@ jobs: with: go-version-file: go.mod cache: true - - name: Restore GOCACHE - uses: actions/cache/restore@v4 - with: - key: go-test-cache-${{ github.ref_name }}-mysql - restore-keys: | - go-test-cache-${{ github.base_ref }}-mysql - go-test-cache-main-mysql - path: /home/runner/.cache/go-build - run: | sudo apt-get update -yq && sudo apt-get install mariadb-client cat devenv/docker/blocks/mysql_tests/setup.sql | mariadb -h 127.0.0.1 -P 3306 -u root -prootpass --disable-ssl-verify-server-cert make gen-go go test -tags=mysql -p=1 -timeout=5m -run '^TestIntegration' $(find ./pkg -type f -name '*_test.go' -exec grep -l '^func TestIntegration' '{}' '+' | grep -o '\(.*\)/' | sort -u) - - name: Save GOCACHE - uses: actions/cache/save@v4 - with: - key: go-test-cache-${{ github.ref_name }}-mysql - path: /home/runner/.cache/go-build postgres: name: Postgres runs-on: ubuntu-latest-8-cores @@ -102,14 +76,6 @@ jobs: with: go-version-file: go.mod cache: true - - name: Restore GOCACHE - uses: actions/cache/restore@v4 - with: - key: go-test-cache-${{ github.ref_name }}-postgres - restore-keys: | - go-test-cache-${{ github.base_ref }}-postgres - go-test-cache-main-postgres - path: /home/runner/.cache/go-build - env: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest @@ -119,8 +85,3 @@ jobs: psql -p 5432 -h 127.0.0.1 -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql make gen-go go test -p=1 -tags=postgres -timeout=5m -run '^TestIntegration' $(find ./pkg -type f -name '*_test.go' -exec grep -l '^func TestIntegration' '{}' '+' | grep -o '\(.*\)/' | sort -u) - - name: Save GOCACHE - uses: actions/cache/save@v4 - with: - key: go-test-cache-${{ github.ref_name }}-postgres - path: /home/runner/.cache/go-build From af33ffd383617962ee29a234112a0cde852c1436 Mon Sep 17 00:00:00 2001 From: "alerting-team[bot]" <158350966+alerting-team[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 13:22:19 -0400 Subject: [PATCH 137/146] Alerting: Update alerting module to be11a2ae18bb786b6c35a6b99a6a876fada9fd6d (#104563) [create-pull-request] automated change Co-authored-by: yuri-tceretian <25988953+yuri-tceretian@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 68709329747..5aaa373613b 100644 --- a/go.mod +++ b/go.mod @@ -77,7 +77,7 @@ require ( github.com/googleapis/go-sql-spanner v1.11.1 // @grafana/grafana-search-and-storage github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group github.com/gorilla/websocket v1.5.3 // @grafana/grafana-app-platform-squad - github.com/grafana/alerting v0.0.0-20250418140648-7268b2b6d9f1 // @grafana/alerting-backend + github.com/grafana/alerting v0.0.0-20250425150043-be11a2ae18bb // @grafana/alerting-backend github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a // @grafana/identity-access-team github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d // @grafana/identity-access-team github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics @@ -455,7 +455,7 @@ require ( github.com/mithrandie/ternary v1.1.1 // indirect github.com/moby/spdystream v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/mschoch/smat v0.2.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect diff --git a/go.sum b/go.sum index 012e22ebd0e..797bef5179e 100644 --- a/go.sum +++ b/go.sum @@ -1565,8 +1565,8 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/alerting v0.0.0-20250418140648-7268b2b6d9f1 h1:xdTzIQqFJxzRLgODsFMAT/8LIWtqCXLqltaWo6AmFVA= -github.com/grafana/alerting v0.0.0-20250418140648-7268b2b6d9f1/go.mod h1:XTbf+jPVVMC1C2NuSGa3hIVbO+KSQD0qHjiUr1GoWVI= +github.com/grafana/alerting v0.0.0-20250425150043-be11a2ae18bb h1:Nkqatz7R7K/2YaK+7pw8CAYgylKfksPVeT7gMARKjwI= +github.com/grafana/alerting v0.0.0-20250425150043-be11a2ae18bb/go.mod h1:pMfhRxL2LZ3Pm8iy7VcVsb9CLYuBtjFYbf1oxgx7yFA= github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a h1:irEH0Abl6mKbkPx/xtmB5Wai4ipzEB6hGPNsLya/p9Y= github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us= github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d h1:34E6btDAhdDOiSEyrMaYaHwnJpM8w9QKzVQZIBzLNmM= From 13eaba98d505fb783af90d36e8e6c8b7bc8538e6 Mon Sep 17 00:00:00 2001 From: Kevin Minehart <5140827+kminehart@users.noreply.github.com> Date: Fri, 25 Apr 2025 12:52:52 -0500 Subject: [PATCH 138/146] CI: Use pull_request_target for PR patch check (#104567) Use pull_request_target for PR patch check --- .github/workflows/pr-patch-check-event.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/pr-patch-check-event.yml b/.github/workflows/pr-patch-check-event.yml index 03dd31a7539..e93d870adf8 100644 --- a/.github/workflows/pr-patch-check-event.yml +++ b/.github/workflows/pr-patch-check-event.yml @@ -3,7 +3,7 @@ name: Dispatch check for patch conflicts run-name: dispatch-check-patch-conflicts-${{ github.base_ref }}-${{ github.head_ref }} on: - pull_request: + pull_request_target: types: - opened - reopened @@ -26,7 +26,6 @@ jobs: # App needs Actions: Read/Write for the grafana/security-patch-actions repo app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} - - name: "Dispatch job" uses: actions/github-script@v7 with: From fe62a7a70853d0d70b50f0b2597a47e43b6dbf66 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 21:02:50 +0000 Subject: [PATCH 139/146] Update dependency marked to v15.0.11 (#104570) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- packages/grafana-data/package.json | 2 +- yarn.lock | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 7cb59e9dc15..041d7dd474b 100644 --- a/package.json +++ b/package.json @@ -348,7 +348,7 @@ "lru-cache": "11.0.2", "lru-memoize": "^1.1.0", "lucene": "^2.1.1", - "marked": "15.0.6", + "marked": "15.0.11", "memoize-one": "6.0.0", "micro-memoize": "^4.1.2", "ml-regression-polynomial": "^3.0.0", diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index 5b4380cc03c..8b5a31cf286 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -67,7 +67,7 @@ "fast_array_intersect": "1.1.0", "history": "4.10.1", "lodash": "4.17.21", - "marked": "15.0.6", + "marked": "15.0.11", "marked-mangle": "1.1.10", "moment": "2.30.1", "moment-timezone": "0.5.47", diff --git a/yarn.lock b/yarn.lock index 45f8c9a978f..01e155919f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2989,7 +2989,7 @@ __metadata: fast_array_intersect: "npm:1.1.0" history: "npm:4.10.1" lodash: "npm:4.17.21" - marked: "npm:15.0.6" + marked: "npm:15.0.11" marked-mangle: "npm:1.1.10" moment: "npm:2.30.1" moment-timezone: "npm:0.5.47" @@ -17875,7 +17875,7 @@ __metadata: lru-cache: "npm:11.0.2" lru-memoize: "npm:^1.1.0" lucene: "npm:^2.1.1" - marked: "npm:15.0.6" + marked: "npm:15.0.11" memoize-one: "npm:6.0.0" micro-memoize: "npm:^4.1.2" mini-css-extract-plugin: "npm:2.9.2" @@ -21891,12 +21891,12 @@ __metadata: languageName: node linkType: hard -"marked@npm:15.0.6": - version: 15.0.6 - resolution: "marked@npm:15.0.6" +"marked@npm:15.0.11": + version: 15.0.11 + resolution: "marked@npm:15.0.11" bin: marked: bin/marked.js - checksum: 10/81c6949655fa6bf0478ab73896cc4be8b7f8b5b755aa1fbfc7cbe3f936cda201dd94dddd09e842e57fffc099c72bef5fe49ec0fa5d7560ee81cc5240860094ce + checksum: 10/939e75f3e989ef4d72d6da9c7e80e43d49ffdc7af8ae2a38cc8c73f20a629d9659a0acda1d0cb5a5f90876cbfb1520d029f98790a934b46592dfa178fbd2838c languageName: node linkType: hard From 759aa12d2bb0d1d6bddba1aef2487f9d5e862b11 Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Fri, 25 Apr 2025 22:07:43 +0100 Subject: [PATCH 140/146] Revert "Build swagger ui in seperate webpack build" (#104580) Revert "Build swagger ui in seperate webpack build (#102046)" This reverts commit 314e337d76df9adcecfd30f4a302fa9d73e56da9. --- .gitignore | 1 - Makefile | 1 - go.work.sum | 2 - package.json | 1 - pkg/api/dtos/index.go | 8 + pkg/api/frontendsettings.go | 2 +- pkg/api/index.go | 2 +- pkg/api/swagger.go | 2 +- .../testdata/sample-assets-manifest.json | 144 ++++++++++++++---- pkg/api/webassets/webassets.go | 52 +++++-- pkg/api/webassets/webassets_test.go | 110 ++++++++++--- pkg/middleware/recovery.go | 2 +- pkg/services/frontend/index.go | 2 +- project.json | 11 -- public/views/swagger.html | 7 +- scripts/webpack/webpack.common.js | 1 + scripts/webpack/webpack.swagger.js | 27 ---- 17 files changed, 259 insertions(+), 116 deletions(-) delete mode 100644 scripts/webpack/webpack.swagger.js diff --git a/.gitignore b/.gitignore index 285c839f370..5ddb7c03ed1 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ awsconfig /.awcache /dist /public/build -/public/build-swagger /emails/dist /reports /e2e/tmp diff --git a/Makefile b/Makefile index 7d7b366aa76..60763468e11 100644 --- a/Makefile +++ b/Makefile @@ -471,7 +471,6 @@ clean: ## Clean up intermediate build artifacts. @echo "cleaning" rm -rf node_modules rm -rf public/build - rm -rf public/build-swagger .PHONY: gen-ts gen-ts: diff --git a/go.work.sum b/go.work.sum index 29ca90c46f7..60f369552b9 100644 --- a/go.work.sum +++ b/go.work.sum @@ -826,7 +826,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk= github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM= github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo= @@ -1076,7 +1075,6 @@ github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qA github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/googleapis/cloud-bigtable-clients-test v0.0.2 h1:S+sCHWAiAc+urcEnvg5JYJUOdlQEm/SEzQ/c/IdAH5M= github.com/googleapis/cloud-bigtable-clients-test v0.0.2/go.mod h1:mk3CrkrouRgtnhID6UZQDK3DrFFa7cYCAJcEmNsHYrY= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= diff --git a/package.json b/package.json index 041d7dd474b..1fb6e4e45f5 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,6 @@ "stats": "webpack --mode production --config scripts/webpack/webpack.prod.js --profile --json > compilation-stats.json", "storybook": "yarn workspace @grafana/ui storybook --ci", "storybook:build": "yarn workspace @grafana/ui storybook:build", - "swaggerui:build": "webpack --config scripts/webpack/webpack.swagger.js --progress", "themes-generate": "esbuild --target=es6 ./scripts/cli/generateSassVariableFiles.ts --bundle --platform=node --tsconfig=./scripts/cli/tsconfig.json | node", "themes:usage": "eslint . --ignore-pattern '*.test.ts*' --ignore-pattern '*.spec.ts*' --cache --plugin '@grafana' --rule '{ @grafana/theme-token-usage: \"error\" }'", "typecheck": "tsc --noEmit && yarn run packages:typecheck", diff --git a/pkg/api/dtos/index.go b/pkg/api/dtos/index.go index ad24aeb9033..2a55017e613 100644 --- a/pkg/api/dtos/index.go +++ b/pkg/api/dtos/index.go @@ -42,6 +42,8 @@ type EntryPointAssets struct { CSSFiles []EntryPointAsset `json:"cssFiles"` Dark string `json:"dark"` Light string `json:"light"` + Swagger []EntryPointAsset `json:"swagger"` + SwaggerCSSFiles []EntryPointAsset `json:"swaggerCssFiles"` } type EntryPointAsset struct { @@ -62,4 +64,10 @@ func (a *EntryPointAssets) SetContentDeliveryURL(prefix string) { for i, p := range a.CSSFiles { a.CSSFiles[i].FilePath = prefix + p.FilePath } + for i, p := range a.Swagger { + a.Swagger[i].FilePath = prefix + p.FilePath + } + for i, p := range a.SwaggerCSSFiles { + a.SwaggerCSSFiles[i].FilePath = prefix + p.FilePath + } } diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 6a001ea44a2..5333033d2bc 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -73,7 +73,7 @@ func (hs *HTTPServer) GetFrontendAssets(c *contextmodel.ReqContext) { // Assets hash.Reset() - dto, err := webassets.GetWebAssets(c.Req.Context(), "build", hs.Cfg, hs.License) + dto, err := webassets.GetWebAssets(c.Req.Context(), hs.Cfg, hs.License) if err == nil && dto != nil { _, _ = hash.Write([]byte(dto.ContentDeliveryURL)) _, _ = hash.Write([]byte(dto.Dark)) diff --git a/pkg/api/index.go b/pkg/api/index.go index fe2e426e3bd..016a78366c6 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -114,7 +114,7 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV } theme := hs.getThemeForIndexData(prefs.Theme, urlPrefs.Theme) - assets, err := webassets.GetWebAssets(c.Req.Context(), "build", hs.Cfg, hs.License) + assets, err := webassets.GetWebAssets(c.Req.Context(), hs.Cfg, hs.License) if err != nil { return nil, err } diff --git a/pkg/api/swagger.go b/pkg/api/swagger.go index c35b08645bd..6e34efdf82a 100644 --- a/pkg/api/swagger.go +++ b/pkg/api/swagger.go @@ -23,7 +23,7 @@ func (hs *HTTPServer) registerSwaggerUI(r routing.RouteRegister) { // The swagger based api navigator r.Get("/swagger", func(c *contextmodel.ReqContext) { ctx := c.Req.Context() - assets, err := webassets.GetWebAssets(ctx, "build-swagger", hs.Cfg, hs.License) + assets, err := webassets.GetWebAssets(ctx, hs.Cfg, hs.License) if err != nil { errhttp.Write(ctx, err, c.Resp) return diff --git a/pkg/api/webassets/testdata/sample-assets-manifest.json b/pkg/api/webassets/testdata/sample-assets-manifest.json index 03ba91f1aeb..7e6791143a0 100644 --- a/pkg/api/webassets/testdata/sample-assets-manifest.json +++ b/pkg/api/webassets/testdata/sample-assets-manifest.json @@ -1,49 +1,135 @@ { - "1085.e73a523ceed532bcdab0.js": { - "src": "public/build/runtime.d0d77f9ceac402028085.js", - "integrity": "sha384-AAAA" + "AdminAuthentication.js": { + "src": "public/build/AdminAuthentication.abcc504db867d1fa3469.js", + "integrity": "sha256-GtJEBbWfWAU2h2hoW+OiHUHNhHuKDzJ6mBEsVKyRc6M= sha384-SfBCROVbdjMZbxWZzYcYq1Ur4qVOmMgCQwRj9rc23op01045mOoRnCSwnENSNnhY sha512-RwKiHjN7M4Wf5BtO0nKFMoZTp0mCPVttkg4SU76+93VVdf8XEnTIpM6Trd+v4GX37wNrWEd34lh/Xaidq5t33g==" }, - "1085.0549a3fcb50e73c4b256.js": { - "src": "public/build/6029.0549a3fcb50e73c4b256.js", - "integrity": "sha384-BBBB" + "AdminEditOrgPage.js": { + "src": "public/build/AdminEditOrgPage.960dc1174d73f4fd46c3.js", + "integrity": "sha256-y9TYWD3xHk5MQiBpvSpKvFgtp7yHovE0hfrUppxsQuw= sha384-kCrsPv6DmRzbRW/ajBaGu5X67nNRRAuOaZZYONCm8YA6KQclrEHG8navPSsMROnk sha512-2oE8w7f0s/vo6SOgDteuYSvuvAAjqMFjPpD4t7b11sah4B007L9dIVs9MF3jTSKsSHlG61hK6L2QCgv+9FtrFg==" }, - "1085.ab25b0e84da80ffc7244.js": { - "src": "public/build/grafana.app.ab25b0e84da80ffc7244.css", - "integrity": "sha384-CCCC" + "AdminFeatureTogglesPage.js": { + "src": "public/build/AdminFeatureTogglesPage.999db7797f1efddde074.js", + "integrity": "sha256-fhZz2BcvirKLQlxqcEprDplsSRAWYu+FiZWMRtFig1g= sha384-V2e+1wmlsKjorbNlJNzDfizXd1E4q8VpqaIX8S8FkumdZwFPPCXMsnHd77rjFKks sha512-LSiPxyiP0tmZ7fS/QOQBnC98pqgLDSATU0V9He5ubNEgziolxd/XmqoSDTGtp1WWZfjDxW6SUoKVmwstc0VzxA==" + }, + "AdminListOrgsPage.js": { + "src": "public/build/AdminListOrgsPage.c573cf876050fc991990.js", + "integrity": "sha256-RSxk6cc52+j9cITdwi4k5lgzER+sKF7Mx5pn6atMYdE= sha384-xE53wdhi0O9e9A+9NKHGlvJgEeSew6ooXYl6oS8e5Qq9slP2TQygvO48/3CCYWJM sha512-YfXZHm9jyZbHAzcAimOv96ZJWEpQrzjZPxxvyVZZVPfB/8Z1FL7rhKhk0HGIhufXTTLhobZp/9CkFKT4no7zTA==" + }, + "AdminSettings.js": { + "src": "public/build/AdminSettings.cfdfc2e959a29968d661.js", + "integrity": "sha256-v/5L4MXpTnSZXjApozisGbt+y+DPfpjWhH3D6bY83zE= sha384-FTIo/3tLgPKqgxwpxXSa8SLoU5hz4HJcgrVgXD2g06/GUnBFLrJcN3lYqYrvsgLu sha512-kBBoMB2XBtXM/iWW0zAFdI9O3ggetQZdpj6LhsiPDLy9pwRhwPQlnwZjlFkJMMEX0YK2uYl3dn9DUUyTKjtVMg==" + }, + "AlertAmRoutes.js": { + "src": "public/build/AlertAmRoutes.bd900447d272cba91413.js", + "integrity": "sha256-K6hY1ruuidLNg5aYV4XZVBpTFCcS7T2q4b1mF9lPOf4= sha384-SFNQUdxgwLjHW/JsukI7Juhs6f2+qdxMCNbsD4ix3Ha44HbiXTKPBTmvxMl4r+CS sha512-eSrh2wrOXlLp9bXd+0zsZS4v8xjJTufSi0Kfgx0FSB8WhnjhOnIOX37wszsV00w1AwyO6VE6yN3q2PGTrpUoeg==" + }, + "AlertGroups.js": { + "src": "public/build/AlertGroups.d04aee7a0d92a3726e5c.js", + "integrity": "sha256-GrsAPOHbrj3qJx97ugYdqF7fEtD1KU+VGZC9EnCzXhM= sha384-WjJwpX218WLCpDgRSkYbHvwjFy0ttlIb9fYpG0pLDgRu9iYQKBZXaYMU9PDKB4Iu sha512-hpE6YahXnP4RSOJ3Xd+oS55gmtDWHDnMrDNr2UOXpWbLuQQJu8e8uRAW5cIBl8u2S63F1SC1vGx6d535hECq2A==" + }, + "AlertRuleListIndex.js": { + "src": "public/build/AlertRuleListIndex.4682f9ab8dd5a8e68722.js", + "integrity": "sha256-xlMu10aTkvXAw8hmtsaEZXTrAqQjaglNkwS6GLRISnU= sha384-hTb2gf6p1Q/M7fULhnDzGOlVjuYjnrVAJCrQ+yNNr0T0EWZkJ+yxrjNh9Q7/9F+G sha512-8xMEU7qfa+rlm62FHWYFb4JNN3E0WNqbhjVdNA2Awq+ZbhSHuzb4XPnmDJoOzakZoG1s+ZgK7GL1wSXBrKbPkw==" + }, + "AlertSilences.js": { + "src": "public/build/AlertSilences.84de2bedd3634a40f4a4.js", + "integrity": "sha256-LamykmTC703pkL5q7hPdOp9zTQWn6ZsMKzRv7v35ubU= sha384-VWDeB7KZFOYL0RWBBXcmvzKds+Yxn2cDU8fSo4Srg165GY29s8+jO6sEG6OY9zj9 sha512-UvlIwoREIwdwkRWdut8+p+GGRgWTyAijW3pNQ1lQRUE8wGNYqBuHa1lGK6Jq4mmhkp1Pqo4/iI4Uk7vrbYFKow==" + }, + "app.css": { + "src": "public/build/grafana.app.91aaa9d81398c147a57c.css", + "integrity": "sha256-77rfikk+dYkH82TOmcmleVoDOHZQdhzVX9gDLcgPbtQ= sha384-IOTlZ1IvTVq5ekKLoaE3/SoZ12K1eExOAnSw9BzkgQ3+RcyQpb1S5hO2w//IIkRB sha512-0Ct3uJBFQIkyxYTvMxseA1cphe2RivXQ2MCbiV0hEm5NzWPiY9sq2P4ay5dXz5v35c++4W47KaknoWlc83bQJQ==" + }, + "app.js": { + "src": "public/build/app.js", + "integrity": "sha256-IOZKp3piC3vddDXP5jy5rIw0vb0KKEOg/k9EGrIxskk= sha384-CBNr5W0pJ23LQMnz5BZI1iVBIExOmF/wpqkEnBtYu9R/yYJIzjpv8KT0a3TBulOi sha512-ockzlzgosuZvLittZrSzh8lexEIZF9iKpy6J9Ii4es3e4D34FpWhHJhDZGpxLVraX4ypLofrDp2Yy0sdUbdi7w==" + }, + "dark.css": { + "src": "public/build/grafana.dark.722d809dba5a31f57d49.css", + "integrity": "sha256-kyPBeKRFdG+i4aLA6vZ0nIkSD25YTHjcCfQ3e2+M6AA= sha384-o2yYT3suDP1EtvTj4UzLsSS+AAlv0F+n5YVaxDePRM6LSGAEPTxFOfxB3myAqD/x sha512-H3Eos9Ff7d+tB7e+6RORDXY49LjNMa8X3ZxiFn8T/OlHUFRnsfXsvj65p9cMqaRom/4qqx0JtmhtnuBoeaysrg==" + }, + "dark.js": { + "src": "public/build/dark.js", + "integrity": "sha256-ON8tNAimyYNoju+WIQP7ux1Aq/oYspSbqoOKvVkeEJY= sha384-figjklDEMn19kCLZ9aq9pUeMGVOC3nlFkQI0PT016cep5KvAd4nyrAl4WA1MALHX sha512-P1xsw+PNS2AVneN4UFfYhYiS3FeEIDPAj4VXAjiQtHqw7qLETqv2K0sYRogoXma23Q/b+h3VL+fw6qtwhJ8WCQ==" + }, + "default-packages_grafana-data_src_field_fieldComparers_ts-packages_grafana-data_src_transform-9dcf20.js": { + "src": "public/build/default-packages_grafana-data_src_field_fieldComparers_ts-packages_grafana-data_src_transform-9dcf20.js", + "integrity": "sha256-BfaH57oBHA9HC5GwpCNAREwyuEGsxbPR+eOEarSMqr0= sha384-elHEqjfd9Guir6jsvCIBsh/wCF4oJ6/wTFfgK9TsKyfyUJecmK3y1qlczBevMotL sha512-92Uk9x490/MViSclM7PjI6iQrM3FNRuiHGP5CWqNFjKX7PWLTcBirSs1xkoZ3tc0QG0m+klaJg6eNVEvpF25dQ==" + }, + "default-packages_grafana-schema_src_raw_composable_barchart_panelcfg_x_BarChartPanelCfg_types-d3da31.js": { + "src": "public/build/default-packages_grafana-schema_src_raw_composable_barchart_panelcfg_x_BarChartPanelCfg_types-d3da31.js", + "integrity": "sha256-elDlrzSExWWpznC49+xQWQletnWQ1Fl6VaarANz2oLk= sha384-2UdE8X6NbOjt/i0WXpvew+eSBXKSTySfFdfxrIrALT0erSyvdbB9oASWPfRuTZyi sha512-so/nTAcSfmcReoZrJUusUbj6xxtGdrKiJ50n0CMZ1yqBh2hB7Y15s4POeX8axeJTNlo59wJ5GAbsdVTy4c0QhA==" + }, + "default-packages_grafana-ui_src_components_Layout_Stack_Stack_tsx-packages_grafana-ui_src_com-2a3620.js": { + "src": "public/build/default-packages_grafana-ui_src_components_Layout_Stack_Stack_tsx-packages_grafana-ui_src_com-2a3620.js", + "integrity": "sha256-+0bPuBGKFGglkXvW4oPiolrNveozRLZVLUrbCYsbVcM= sha384-EIayAgykdDWmyilAuXo4ad96v3tRqdWZp+BHdeDpSSsbAMeg+eoBBbk2Yh219kDg sha512-+jn7kmQ9Id8aTIe66TD+vM+W19cTIVexEfkxbxgqXdJyJ72qalN6ccWyP1ro1w/E1R/laZGNLz1LBc1I4u2Isw==" + }, + "defaultVendors-node_modules_braintree_sanitize-url_dist_index_js-node_modules_emotion_css_dis-e9d917.js": { + "src": "public/build/defaultVendors-node_modules_braintree_sanitize-url_dist_index_js-node_modules_emotion_css_dis-e9d917.js", + "integrity": "sha256-6et8tLQscwAftNYIM4u8L42Xi+tFIlkPsKIlyATTuY4= sha384-9AKIelnSKqxpMktU6Fw1uE1Kz6ZuEtI2ExvQ1PTjwPmpAE41LS6WxQ3pSV6qlLyz sha512-zcqwJExa8qzqZ1cCIK/y+HrWhJxP6rgSC+SbqxRStQUZrU4S1DQa1pwe6eSoZ4DPht/8rSeIii1wnl3DyhdVVg==" + }, + "defaultVendors-node_modules_fingerprintjs_fingerprintjs_dist_fp_esm_js-node_modules_grafana_e-45bb7f.js": { + "src": "public/build/defaultVendors-node_modules_fingerprintjs_fingerprintjs_dist_fp_esm_js-node_modules_grafana_e-45bb7f.js", + "integrity": "sha256-bVtkMWml/TQfwbkV9ueteGi+GhLh7ajxtVevi3gb4Aw= sha384-8+VsDObIkyeW90JyQELbiOsPKFJaBOmScMhBuWKmhtBmV9/7YvjIvqEPvpz1H5gP sha512-L3PzrsTEqfR+ruNzEaIqt0V0ZyX4V1fqbOe7aXWMIOr8JjJSJT1rMPVqJixy5DOL2f0zLDEc/DJvk4SN4BrDuw==" + }, + "defaultVendors-node_modules_swagger-ui-react_index_mjs.js": { + "src": "public/build/defaultVendors-node_modules_swagger-ui-react_index_mjs.js", + "integrity": "sha256-7qtYNxawpAwQUd6KR+2IgH95hkr0w5hZLGFeubAUkcc= sha384-29fQncD6F/vaoKmk4Ccht9JbnrPo2BSyUdHSkTkF4RWEFkhgVEUo1TlSTDc3iOcd sha512-UhXtPd5nXyvyVR0vNYoO5wINJgb49oeYoqfnNaVQndg3n9uIvPi4lr3iUk/jkBsnVH1dEDWiZIErDPiTyHe4+w==" + }, + "light.css": { + "src": "public/build/grafana.light.2fbd901d840329c18394.css", + "integrity": "sha256-OumSnRJ7qttDoFmbB3LMxVvrfpFFjABX5b1iZDcRMmk= sha384-Cq4wA1zPU1cqxA/pc2V+iMwVjzsSdL09vM8xg4fSofGen7YIj6sVUgs2X4AdTzm/ sha512-6J9O/4UFL8undkyqbEGAlNnEjm5N8Us5k9rJdcjhZwYKt1UIKXapw8f320k3tv1N0GB0Wt4xO6EKD9MsrIkCSw==" + }, + "light.js": { + "src": "public/build/light.js", + "integrity": "sha256-TDwcqlc6Pc8yTRNLOk0jp3Wnp06cnF/OSaFvwOnwCVQ= sha384-xJv6KB8Qm9Skru8PY5CRdU9a59e94tcYWonpy5rqaci6fiecQM3aH+eHe41succB sha512-sPXciUAa7rpRofN6b93v9+tWpLP88ER6TqAGpbHkz69mxuQLJv1p580fEfxLeIs3HfLkDXnVPK7rGF0d5Evrog==" + }, + "moment-node_modules_moment_locale_af_js-node_modules_moment_locale_ar-dz_js-node_modules_mome-ddcc36.js": { + "src": "public/build/moment-node_modules_moment_locale_af_js-node_modules_moment_locale_ar-dz_js-node_modules_mome-ddcc36.js", + "integrity": "sha256-BvrNPeEZbt43PpUlZ4r5pUougn3OzHIHKZN9pa5fotw= sha384-gMrZ0j/kyHwx2bhyt5ZjZOPlK7P7TRN6rBnk/0c9iF2X83GVTWNjhHcldmlYJXnf sha512-B+39Sk+Rwdo9hURcDoWL/61/sbMsTlqfES+FCsMC1r2xUH/Fyw/U2H0CgIi+c3AJyu1GLEvp//QBN0RZxaizhw==" + }, + "runtime.js": { + "src": "public/build/runtime.js", + "integrity": "sha256-tM4AGASn3Cb8139+wp3w6rlo3ELFAuUW7K4Pifx226o= sha384-DfxxsYWb0+RxiXOr+wtCSzAAYGecffq/iHyn6CN9tHmaORv1sS+rsrnlnJo2jPQD sha512-qSxdqrx0mJLY1mdkbKrkCyqOoIEgFqzCoY9+uIuFRIVDPFbb2nJy0NtaKMQvDJnAzIrJFwzwW1e250T4WqQNiQ==" + }, + "swagger.css": { + "src": "public/build/grafana.swagger.2733d417270d5dd49373.css", + "integrity": "sha256-GNcHNgIAT7S+J4X7seFjlvNPC1bRhM15d0cQBm3VFoQ= sha384-ywztCBf8uF0tTFjC1mLth33RI2WuFURN3dRy7Bv2PheGzbWJpwlgo9+mtT2Zm7mO sha512-e4c+VedZGqcwLqwfdqRWonggRPO0gjJ7Z0YbXK5z4bFTsUIc+x8ycIJG+eQaf8cuHlsakG4hkWNkRwLBazcFAg==" + }, + "swagger.js": { + "src": "public/build/swagger.js", + "integrity": "sha256-wLlip7zRYODW/TPcI5JZPRdmWirc1KD+UcNF+8V9RBk= sha384-6VGD+LgCpjMZN/ORSjWcrWa9diUzQO3OfEhP0D2ZluSwP4IT+0kH7KEeD9NVbojd sha512-vZOCFzBZBhd34yGv8z7P4Gw4WLVR9HjpuK0y6Kcw+pCBk5Dv9qHBg3ZVs6s0tOnUmiMWwgL4Ne8f+zgiuJVPqg==" }, "entrypoints": { "app": { "assets": { "js": [ - "public/build/runtime.d0d77f9ceac402028085.js", - "public/build/6029.0549a3fcb50e73c4b256.js" + "public/build/runtime.js", + "public/build/default-packages_grafana-ui_src_components_Layout_Stack_Stack_tsx-packages_grafana-ui_src_com-2a3620.js", + "public/build/app.js" ], - "css": [ - "public/build/grafana.app.ab25b0e84da80ffc7244.css" - ] + "css": ["public/build/grafana.app.91aaa9d81398c147a57c.css"] + } + }, + "swagger": { + "assets": { + "js": [ + "public/build/runtime.js", + "public/build/swagger.js" + ], + "css": ["public/build/grafana.swagger.2733d417270d5dd49373.css"] } }, "dark": { "assets": { - "js": [ - "public/build/runtime.d0d77f9ceac402028085.js", - "public/build/dark.d9196c1e81619cd5ae4f.js" - ], - "css": [ - "public/build/grafana.dark.23c5425b7a9e1580d499.css" - ] + "js": ["public/build/runtime.js", "public/build/dark.js"], + "css": ["public/build/grafana.dark.722d809dba5a31f57d49.css"] } }, "light": { "assets": { - "js": [ - "public/build/runtime.d0d77f9ceac402028085.js", - "public/build/light.6f4baca9576edc9c2e5b.js" - ], - "css": [ - "public/build/grafana.light.7fb85ddc153a7c559092.css" - ] + "js": ["public/build/runtime.js", "public/build/light.js"], + "css": ["public/build/grafana.light.2fbd901d840329c18394.css"] } } } -} \ No newline at end of file +} diff --git a/pkg/api/webassets/webassets.go b/pkg/api/webassets/webassets.go index bdab5665ba6..b0cd7ab09c9 100644 --- a/pkg/api/webassets/webassets.go +++ b/pkg/api/webassets/webassets.go @@ -20,9 +20,10 @@ type ManifestInfo struct { Integrity string `json:"integrity,omitempty"` // The known entrypoints - App *EntryPointInfo `json:"app,omitempty"` - Dark *EntryPointInfo `json:"dark,omitempty"` - Light *EntryPointInfo `json:"light,omitempty"` + App *EntryPointInfo `json:"app,omitempty"` + Dark *EntryPointInfo `json:"dark,omitempty"` + Light *EntryPointInfo `json:"light,omitempty"` + Swagger *EntryPointInfo `json:"swagger,omitempty"` } type EntryPointInfo struct { @@ -37,7 +38,7 @@ var ( entryPointAssetsCache *dtos.EntryPointAssets // TODO: get rid of global state ) -func GetWebAssets(ctx context.Context, build string, cfg *setting.Cfg, license licensing.Licensing) (*dtos.EntryPointAssets, error) { +func GetWebAssets(ctx context.Context, cfg *setting.Cfg, license licensing.Licensing) (*dtos.EntryPointAssets, error) { entryPointAssetsCacheMu.RLock() ret := entryPointAssetsCache entryPointAssetsCacheMu.RUnlock() @@ -53,11 +54,11 @@ func GetWebAssets(ctx context.Context, build string, cfg *setting.Cfg, license l cdn := "" // "https://grafana-assets.grafana.net/grafana/10.3.0-64123/" if cdn != "" { - result, err = readWebAssetsFromCDN(ctx, build, cdn) + result, err = readWebAssetsFromCDN(ctx, cdn) } if result == nil { - result, err = readWebAssetsFromFile(filepath.Join(cfg.StaticRootPath, build, "assets-manifest.json")) + result, err = readWebAssetsFromFile(filepath.Join(cfg.StaticRootPath, "build", "assets-manifest.json")) if err == nil { cdn, _ = cfg.GetContentDeliveryURL(license.ContentDeliveryPrefix()) if cdn != "" { @@ -82,8 +83,8 @@ func readWebAssetsFromFile(manifestpath string) (*dtos.EntryPointAssets, error) return readWebAssets(f) } -func readWebAssetsFromCDN(ctx context.Context, build string, baseURL string) (*dtos.EntryPointAssets, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL+"public/"+build+"/assets-manifest.json", nil) +func readWebAssetsFromCDN(ctx context.Context, baseURL string) (*dtos.EntryPointAssets, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL+"public/build/assets-manifest.json", nil) if err != nil { return nil, err } @@ -122,16 +123,23 @@ func readWebAssets(r io.Reader) (*dtos.EntryPointAssets, error) { if entryPoints.App == nil || len(entryPoints.App.Assets.JS) == 0 { return nil, fmt.Errorf("missing app entry, try running `yarn build`") } + if entryPoints.Dark == nil || len(entryPoints.Dark.Assets.CSS) == 0 { + return nil, fmt.Errorf("missing dark entry, try running `yarn build`") + } + if entryPoints.Light == nil || len(entryPoints.Light.Assets.CSS) == 0 { + return nil, fmt.Errorf("missing light entry, try running `yarn build`") + } + if entryPoints.Swagger == nil || len(entryPoints.Swagger.Assets.JS) == 0 { + return nil, fmt.Errorf("missing swagger entry, try running `yarn build`") + } rsp := &dtos.EntryPointAssets{ - JSFiles: make([]dtos.EntryPointAsset, 0, len(entryPoints.App.Assets.JS)), - CSSFiles: make([]dtos.EntryPointAsset, 0, len(entryPoints.App.Assets.CSS)), - } - if entryPoints.Dark != nil && len(entryPoints.Dark.Assets.CSS) > 0 { - rsp.Dark = entryPoints.Dark.Assets.CSS[0] - } - if entryPoints.Light != nil && len(entryPoints.Light.Assets.CSS) > 0 { - rsp.Light = entryPoints.Light.Assets.CSS[0] + JSFiles: make([]dtos.EntryPointAsset, 0, len(entryPoints.App.Assets.JS)), + CSSFiles: make([]dtos.EntryPointAsset, 0, len(entryPoints.App.Assets.CSS)), + Dark: entryPoints.Dark.Assets.CSS[0], + Light: entryPoints.Light.Assets.CSS[0], + Swagger: make([]dtos.EntryPointAsset, 0, len(entryPoints.Swagger.Assets.JS)), + SwaggerCSSFiles: make([]dtos.EntryPointAsset, 0, len(entryPoints.Swagger.Assets.CSS)), } for _, entry := range entryPoints.App.Assets.JS { @@ -146,5 +154,17 @@ func readWebAssets(r io.Reader) (*dtos.EntryPointAssets, error) { Integrity: integrity[entry], }) } + for _, entry := range entryPoints.Swagger.Assets.JS { + rsp.Swagger = append(rsp.Swagger, dtos.EntryPointAsset{ + FilePath: entry, + Integrity: integrity[entry], + }) + } + for _, entry := range entryPoints.Swagger.Assets.CSS { + rsp.SwaggerCSSFiles = append(rsp.SwaggerCSSFiles, dtos.EntryPointAsset{ + FilePath: entry, + Integrity: integrity[entry], + }) + } return rsp, nil } diff --git a/pkg/api/webassets/webassets_test.go b/pkg/api/webassets/webassets_test.go index 65e89217285..b4bb5c25d70 100644 --- a/pkg/api/webassets/webassets_test.go +++ b/pkg/api/webassets/webassets_test.go @@ -3,6 +3,7 @@ package webassets import ( "context" "encoding/json" + "fmt" "testing" "github.com/stretchr/testify/require" @@ -15,32 +16,101 @@ func TestReadWebassets(t *testing.T) { dto, err := json.MarshalIndent(assets, "", " ") require.NoError(t, err) // fmt.Printf("%s\n", string(dto)) + require.JSONEq(t, `{ - "jsFiles": [ - { - "filePath": "public/build/runtime.d0d77f9ceac402028085.js", - "integrity": "sha384-AAAA" - }, - { - "filePath": "public/build/6029.0549a3fcb50e73c4b256.js", - "integrity": "sha384-BBBB" - } - ], - "cssFiles": [ - { - "filePath": "public/build/grafana.app.ab25b0e84da80ffc7244.css", - "integrity": "sha384-CCCC" - } - ], - "dark": "public/build/grafana.dark.23c5425b7a9e1580d499.css", - "light": "public/build/grafana.light.7fb85ddc153a7c559092.css" - }`, string(dto)) + "jsFiles": [ + { + "filePath": "public/build/runtime.js", + "integrity": "sha256-tM4AGASn3Cb8139+wp3w6rlo3ELFAuUW7K4Pifx226o= sha384-DfxxsYWb0+RxiXOr+wtCSzAAYGecffq/iHyn6CN9tHmaORv1sS+rsrnlnJo2jPQD sha512-qSxdqrx0mJLY1mdkbKrkCyqOoIEgFqzCoY9+uIuFRIVDPFbb2nJy0NtaKMQvDJnAzIrJFwzwW1e250T4WqQNiQ==" + }, + { + "filePath": "public/build/default-packages_grafana-ui_src_components_Layout_Stack_Stack_tsx-packages_grafana-ui_src_com-2a3620.js", + "integrity": "sha256-+0bPuBGKFGglkXvW4oPiolrNveozRLZVLUrbCYsbVcM= sha384-EIayAgykdDWmyilAuXo4ad96v3tRqdWZp+BHdeDpSSsbAMeg+eoBBbk2Yh219kDg sha512-+jn7kmQ9Id8aTIe66TD+vM+W19cTIVexEfkxbxgqXdJyJ72qalN6ccWyP1ro1w/E1R/laZGNLz1LBc1I4u2Isw==" + }, + { + "filePath": "public/build/app.js", + "integrity": "sha256-IOZKp3piC3vddDXP5jy5rIw0vb0KKEOg/k9EGrIxskk= sha384-CBNr5W0pJ23LQMnz5BZI1iVBIExOmF/wpqkEnBtYu9R/yYJIzjpv8KT0a3TBulOi sha512-ockzlzgosuZvLittZrSzh8lexEIZF9iKpy6J9Ii4es3e4D34FpWhHJhDZGpxLVraX4ypLofrDp2Yy0sdUbdi7w==" + } + ], + "cssFiles": [ + { + "filePath": "public/build/grafana.app.91aaa9d81398c147a57c.css", + "integrity": "sha256-77rfikk+dYkH82TOmcmleVoDOHZQdhzVX9gDLcgPbtQ= sha384-IOTlZ1IvTVq5ekKLoaE3/SoZ12K1eExOAnSw9BzkgQ3+RcyQpb1S5hO2w//IIkRB sha512-0Ct3uJBFQIkyxYTvMxseA1cphe2RivXQ2MCbiV0hEm5NzWPiY9sq2P4ay5dXz5v35c++4W47KaknoWlc83bQJQ==" + } + ], + "dark": "public/build/grafana.dark.722d809dba5a31f57d49.css", + "light": "public/build/grafana.light.2fbd901d840329c18394.css", + "swagger": [ + { + "filePath": "public/build/runtime.js", + "integrity": "sha256-tM4AGASn3Cb8139+wp3w6rlo3ELFAuUW7K4Pifx226o= sha384-DfxxsYWb0+RxiXOr+wtCSzAAYGecffq/iHyn6CN9tHmaORv1sS+rsrnlnJo2jPQD sha512-qSxdqrx0mJLY1mdkbKrkCyqOoIEgFqzCoY9+uIuFRIVDPFbb2nJy0NtaKMQvDJnAzIrJFwzwW1e250T4WqQNiQ==" + }, + { + "filePath": "public/build/swagger.js", + "integrity": "sha256-wLlip7zRYODW/TPcI5JZPRdmWirc1KD+UcNF+8V9RBk= sha384-6VGD+LgCpjMZN/ORSjWcrWa9diUzQO3OfEhP0D2ZluSwP4IT+0kH7KEeD9NVbojd sha512-vZOCFzBZBhd34yGv8z7P4Gw4WLVR9HjpuK0y6Kcw+pCBk5Dv9qHBg3ZVs6s0tOnUmiMWwgL4Ne8f+zgiuJVPqg==" + } + ], + "swaggerCssFiles": [ + { + "filePath": "public/build/grafana.swagger.2733d417270d5dd49373.css", + "integrity": "sha256-GNcHNgIAT7S+J4X7seFjlvNPC1bRhM15d0cQBm3VFoQ= sha384-ywztCBf8uF0tTFjC1mLth33RI2WuFURN3dRy7Bv2PheGzbWJpwlgo9+mtT2Zm7mO sha512-e4c+VedZGqcwLqwfdqRWonggRPO0gjJ7Z0YbXK5z4bFTsUIc+x8ycIJG+eQaf8cuHlsakG4hkWNkRwLBazcFAg==" + } + ] + }`, string(dto)) + + assets.SetContentDeliveryURL("https://grafana-assets.grafana.net/grafana/10.3.0-64123/") + + dto, err = json.MarshalIndent(assets, "", " ") + require.NoError(t, err) + fmt.Printf("%s\n", string(dto)) + + require.JSONEq(t, `{ + "cdn": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/", + "jsFiles": [ + { + "filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/runtime.js", + "integrity": "sha256-tM4AGASn3Cb8139+wp3w6rlo3ELFAuUW7K4Pifx226o= sha384-DfxxsYWb0+RxiXOr+wtCSzAAYGecffq/iHyn6CN9tHmaORv1sS+rsrnlnJo2jPQD sha512-qSxdqrx0mJLY1mdkbKrkCyqOoIEgFqzCoY9+uIuFRIVDPFbb2nJy0NtaKMQvDJnAzIrJFwzwW1e250T4WqQNiQ==" + }, + { + "filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/default-packages_grafana-ui_src_components_Layout_Stack_Stack_tsx-packages_grafana-ui_src_com-2a3620.js", + "integrity": "sha256-+0bPuBGKFGglkXvW4oPiolrNveozRLZVLUrbCYsbVcM= sha384-EIayAgykdDWmyilAuXo4ad96v3tRqdWZp+BHdeDpSSsbAMeg+eoBBbk2Yh219kDg sha512-+jn7kmQ9Id8aTIe66TD+vM+W19cTIVexEfkxbxgqXdJyJ72qalN6ccWyP1ro1w/E1R/laZGNLz1LBc1I4u2Isw==" + }, + { + "filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/app.js", + "integrity": "sha256-IOZKp3piC3vddDXP5jy5rIw0vb0KKEOg/k9EGrIxskk= sha384-CBNr5W0pJ23LQMnz5BZI1iVBIExOmF/wpqkEnBtYu9R/yYJIzjpv8KT0a3TBulOi sha512-ockzlzgosuZvLittZrSzh8lexEIZF9iKpy6J9Ii4es3e4D34FpWhHJhDZGpxLVraX4ypLofrDp2Yy0sdUbdi7w==" + } + ], + "cssFiles": [ + { + "filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/grafana.app.91aaa9d81398c147a57c.css", + "integrity": "sha256-77rfikk+dYkH82TOmcmleVoDOHZQdhzVX9gDLcgPbtQ= sha384-IOTlZ1IvTVq5ekKLoaE3/SoZ12K1eExOAnSw9BzkgQ3+RcyQpb1S5hO2w//IIkRB sha512-0Ct3uJBFQIkyxYTvMxseA1cphe2RivXQ2MCbiV0hEm5NzWPiY9sq2P4ay5dXz5v35c++4W47KaknoWlc83bQJQ==" + } + ], + "dark": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/grafana.dark.722d809dba5a31f57d49.css", + "light": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/grafana.light.2fbd901d840329c18394.css", + "swagger": [ + { + "filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/runtime.js", + "integrity": "sha256-tM4AGASn3Cb8139+wp3w6rlo3ELFAuUW7K4Pifx226o= sha384-DfxxsYWb0+RxiXOr+wtCSzAAYGecffq/iHyn6CN9tHmaORv1sS+rsrnlnJo2jPQD sha512-qSxdqrx0mJLY1mdkbKrkCyqOoIEgFqzCoY9+uIuFRIVDPFbb2nJy0NtaKMQvDJnAzIrJFwzwW1e250T4WqQNiQ==" + }, + { + "filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/swagger.js", + "integrity": "sha256-wLlip7zRYODW/TPcI5JZPRdmWirc1KD+UcNF+8V9RBk= sha384-6VGD+LgCpjMZN/ORSjWcrWa9diUzQO3OfEhP0D2ZluSwP4IT+0kH7KEeD9NVbojd sha512-vZOCFzBZBhd34yGv8z7P4Gw4WLVR9HjpuK0y6Kcw+pCBk5Dv9qHBg3ZVs6s0tOnUmiMWwgL4Ne8f+zgiuJVPqg==" + } + ], + "swaggerCssFiles": [ + { + "filePath": "https://grafana-assets.grafana.net/grafana/10.3.0-64123/public/build/grafana.swagger.2733d417270d5dd49373.css", + "integrity": "sha256-GNcHNgIAT7S+J4X7seFjlvNPC1bRhM15d0cQBm3VFoQ= sha384-ywztCBf8uF0tTFjC1mLth33RI2WuFURN3dRy7Bv2PheGzbWJpwlgo9+mtT2Zm7mO sha512-e4c+VedZGqcwLqwfdqRWonggRPO0gjJ7Z0YbXK5z4bFTsUIc+x8ycIJG+eQaf8cuHlsakG4hkWNkRwLBazcFAg==" + } + ] + }`, string(dto)) } func TestReadWebassetsFromCDN(t *testing.T) { t.Skip() - assets, err := readWebAssetsFromCDN(context.Background(), "build", "https://grafana-assets.grafana.net/grafana/10.3.0-64123/") + assets, err := readWebAssetsFromCDN(context.Background(), "https://grafana-assets.grafana.net/grafana/10.3.0-64123/") require.NoError(t, err) dto, err := json.MarshalIndent(assets, "", " ") diff --git a/pkg/middleware/recovery.go b/pkg/middleware/recovery.go index 63c08172c9b..01e07ee8e38 100644 --- a/pkg/middleware/recovery.go +++ b/pkg/middleware/recovery.go @@ -138,7 +138,7 @@ func Recovery(cfg *setting.Cfg, license licensing.Licensing) web.Middleware { return } - assets, _ := webassets.GetWebAssets(req.Context(), "build", cfg, license) + assets, _ := webassets.GetWebAssets(req.Context(), cfg, license) if assets == nil { assets = &dtos.EntryPointAssets{JSFiles: []dtos.EntryPointAsset{}} } diff --git a/pkg/services/frontend/index.go b/pkg/services/frontend/index.go index 037843e0357..4f02bbe1127 100644 --- a/pkg/services/frontend/index.go +++ b/pkg/services/frontend/index.go @@ -49,7 +49,7 @@ var ( ) func NewIndexProvider(cfg *setting.Cfg, license licensing.Licensing) (*IndexProvider, error) { - assets, err := webassets.GetWebAssets(context.Background(), "build", cfg, license) + assets, err := webassets.GetWebAssets(context.Background(), cfg, license) if err != nil { return nil, err } diff --git a/project.json b/project.json index 62ac256bd91..b67053dbbe6 100644 --- a/project.json +++ b/project.json @@ -5,7 +5,6 @@ "targets": { "start": { "dependsOn": [ - "swaggerui:build", "themes-generate", { "projects": ["tag:scope:plugin"], @@ -15,7 +14,6 @@ }, "build": { "dependsOn": [ - "swaggerui:build", "themes-generate", { "projects": ["tag:scope:plugin"], @@ -37,15 +35,6 @@ "{workspaceRoot}/public/sass/_variables.light.generated.scss" ], "cache": true - }, - "swaggerui:build": { - "inputs": [ - "{workspaceRoot}/packages/grafana-ui/src/themes/**", - "{workspaceRoot}/public/app/core/trustedTypePolicies.ts", - "{workspaceRoot}/public/swagger/**" - ], - "outputs": ["{workspaceRoot}/public/build-swagger"], - "cache": true } } } diff --git a/public/views/swagger.html b/public/views/swagger.html index 464fad8a7f5..44e622e0087 100644 --- a/public/views/swagger.html +++ b/public/views/swagger.html @@ -12,9 +12,10 @@ Grafana API Reference - [[range $asset := .Assets.CSSFiles]] + [[range $asset := .Assets.SwaggerCSSFiles]] [[end]] + @@ -26,11 +27,11 @@
- [[range $asset := .Assets.JSFiles]] + [[range $asset := .Assets.Swagger]] [[end]] diff --git a/scripts/webpack/webpack.common.js b/scripts/webpack/webpack.common.js index 37e8c399066..833438bd51a 100644 --- a/scripts/webpack/webpack.common.js +++ b/scripts/webpack/webpack.common.js @@ -7,6 +7,7 @@ module.exports = { target: 'web', entry: { app: './public/app/index.ts', + swagger: './public/swagger/index.tsx', }, experiments: { // Required to load WASM modules. diff --git a/scripts/webpack/webpack.swagger.js b/scripts/webpack/webpack.swagger.js deleted file mode 100644 index 48c3056877d..00000000000 --- a/scripts/webpack/webpack.swagger.js +++ /dev/null @@ -1,27 +0,0 @@ -// @ts-check -const path = require('path'); - -const makeBaseConfig = require('./webpack.prod.js'); - -module.exports = (env = {}) => { - const baseConfig = makeBaseConfig(env); - - return { - ...baseConfig, - - entry: { - app: './public/swagger/index.tsx', - light: './public/sass/grafana.light.scss', - }, - - // Output to a different directory so each build doesn't clobber each other - output: { - ...baseConfig.output, - clean: true, - path: path.resolve(__dirname, '../../public/build-swagger'), - filename: '[name].[contenthash].js', - // Keep publicPath relative for host.com/grafana/ deployments - publicPath: 'public/build-swagger/', - }, - }; -}; From cf453b46d45d5a908548d07544ec60a1a587b9df Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Fri, 25 Apr 2025 15:51:38 -0600 Subject: [PATCH 141/146] K8s: App Plugins: Fix dashboard updater (#104583) --- pkg/services/plugindashboards/service/dashboard_updater.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/services/plugindashboards/service/dashboard_updater.go b/pkg/services/plugindashboards/service/dashboard_updater.go index efcc49c0545..14b67a2589c 100644 --- a/pkg/services/plugindashboards/service/dashboard_updater.go +++ b/pkg/services/plugindashboards/service/dashboard_updater.go @@ -139,6 +139,7 @@ func (du *DashboardUpdater) syncPluginDashboards(ctx context.Context, plugin plu func (du *DashboardUpdater) handlePluginStateChanged(ctx context.Context, event *pluginsettings.PluginStateChangedEvent) error { du.logger.Info("Plugin state changed", "pluginId", event.PluginId, "enabled", event.Enabled) + ctx, _ = identity.WithServiceIdentity(ctx, event.OrgId) if event.Enabled { p, exists := du.pluginStore.Plugin(ctx, event.PluginId) From d6d5771aff7d720db791053c1b6dd6c6908c2633 Mon Sep 17 00:00:00 2001 From: Pepe Cano <825430+ppcano@users.noreply.github.com> Date: Mon, 28 Apr 2025 10:42:37 +0200 Subject: [PATCH 142/146] alerting: update UI text to clarify recovery threshold for pending state (#102788) * alerting: update UI text to clarify recovery threshold for pending state * Update recent translation --- .../unified/GrafanaRuleQueryViewer.tsx | 4 ++- .../expressions/components/Threshold.tsx | 35 +++++++++++++------ public/locales/en-US/grafana.json | 18 +++++----- 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/public/app/features/alerting/unified/GrafanaRuleQueryViewer.tsx b/public/app/features/alerting/unified/GrafanaRuleQueryViewer.tsx index 781726d1413..71dd85a6799 100644 --- a/public/app/features/alerting/unified/GrafanaRuleQueryViewer.tsx +++ b/public/app/features/alerting/unified/GrafanaRuleQueryViewer.tsx @@ -503,7 +503,9 @@ function ThresholdExpressionViewer({ model }: { model: ExpressionQuery }) { {unloadEvaluator && ( <>
- Stop alerting when + + Stop alerting (or pending state) when{' '} +
{expression}
diff --git a/public/app/features/expressions/components/Threshold.tsx b/public/app/features/expressions/components/Threshold.tsx index e3fa883d918..ebb9ae393aa 100644 --- a/public/app/features/expressions/components/Threshold.tsx +++ b/public/app/features/expressions/components/Threshold.tsx @@ -219,7 +219,7 @@ function RecoveryThresholdRow({ isRange, condition, onError, dispatch, allowOnbl @@ -256,7 +256,7 @@ function RecoveryThresholdRow({ isRange, condition, onError, dispatch, allowOnbl @@ -293,7 +293,7 @@ function RecoveryThresholdRow({ isRange, condition, onError, dispatch, allowOnbl @@ -329,7 +329,7 @@ function RecoveryThresholdRow({ isRange, condition, onError, dispatch, allowOnbl @@ -370,7 +370,10 @@ function RecoveryThresholdRow({ isRange, condition, onError, dispatch, allowOnbl return ( Date: Mon, 28 Apr 2025 10:51:11 +0200 Subject: [PATCH 143/146] chore: Remove unused dependency (#104457) --- public/app/plugins/datasource/tempo/package.json | 1 - yarn.lock | 10 ---------- 2 files changed, 11 deletions(-) diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json index df81acbef1f..b9248534a4f 100644 --- a/public/app/plugins/datasource/tempo/package.json +++ b/public/app/plugins/datasource/tempo/package.json @@ -7,7 +7,6 @@ "@emotion/css": "11.13.5", "@grafana/data": "workspace:*", "@grafana/e2e-selectors": "workspace:*", - "@grafana/lezer-logql": "0.2.6", "@grafana/lezer-traceql": "0.0.21", "@grafana/monaco-logql": "^0.0.8", "@grafana/o11y-ds-frontend": "workspace:*", diff --git a/yarn.lock b/yarn.lock index 01e155919f1..bab3b3d8e73 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2823,7 +2823,6 @@ __metadata: "@emotion/css": "npm:11.13.5" "@grafana/data": "workspace:*" "@grafana/e2e-selectors": "workspace:*" - "@grafana/lezer-logql": "npm:0.2.6" "@grafana/lezer-traceql": "npm:0.0.21" "@grafana/monaco-logql": "npm:^0.0.8" "@grafana/o11y-ds-frontend": "workspace:*" @@ -3170,15 +3169,6 @@ __metadata: languageName: node linkType: hard -"@grafana/lezer-logql@npm:0.2.6": - version: 0.2.6 - resolution: "@grafana/lezer-logql@npm:0.2.6" - peerDependencies: - "@lezer/lr": ^1.0.0 - checksum: 10/09df02b9934f37e9e58731ce0806923618991dfb46510ab29299040860b590359c9083def93176b1076cb1880302a92e527fb5aad0cbf97557a8025c74a997ed - languageName: node - linkType: hard - "@grafana/lezer-logql@npm:0.2.7": version: 0.2.7 resolution: "@grafana/lezer-logql@npm:0.2.7" From d19f86a736bff6ac743af3e1e078b2604347ead9 Mon Sep 17 00:00:00 2001 From: Sven Grossmann Date: Mon, 28 Apr 2025 12:22:46 +0200 Subject: [PATCH 144/146] ci: move variables to `env` (#104605) * ci: move variables to `env` * ci: move sha to `env` * ci: import `SHA` and `PRE_COMMIT_SHA` --- .github/workflows/pr-patch-check-event.yml | 23 +++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pr-patch-check-event.yml b/.github/workflows/pr-patch-check-event.yml index e93d870adf8..e1389fdac6a 100644 --- a/.github/workflows/pr-patch-check-event.yml +++ b/.github/workflows/pr-patch-check-event.yml @@ -17,6 +17,13 @@ on: # target branch onto the source branch, to verify compatibility before merging. jobs: dispatch-job: + env: + HEAD_REF: ${{ github.head_ref }} + BASE_REF: ${{ github.base_ref }} + REPO: ${{ github.repository }} + SENDER: ${{ github.event.sender.login }} + SHA: ${{ github.sha }} + PR_COMMIT_SHA: ${{ github.event.pull_request.head.sha }} runs-on: ubuntu-latest steps: - name: "Generate token" @@ -31,18 +38,20 @@ jobs: with: github-token: ${{ steps.generate_token.outputs.token }} script: | + const {HEAD_REF, BASE_REF, REPO, SENDER, SHA, PR_COMMIT_SHA} = process.env; + await github.rest.actions.createWorkflowDispatch({ owner: 'grafana', repo: 'security-patch-actions', workflow_id: 'test-patches-event.yml', ref: 'main', inputs: { - src_repo: "${{ github.repository }}", - src_ref: "${{ github.head_ref }}", - src_merge_sha: "${{ github.sha }}", - src_pr_commit_sha: "${{ github.event.pull_request.head.sha }}", - patch_repo: "${{ github.repository }}-security-patches", - patch_ref: "${{ github.base_ref }}", - triggering_github_handle: "${{ github.event.sender.login }}" + src_repo: REPO, + src_ref: HEAD_REF, + src_merge_sha: SHA, + src_pr_commit_sha: PR_COMMIT_SHA, + patch_repo: REPO + '-security-patches', + patch_ref: BASE_REF, + triggering_github_handle: SENDER } }) From 60b03a5a105eee5e079f8b88c8a184ea065cbbd6 Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Mon, 28 Apr 2025 11:30:26 +0100 Subject: [PATCH 145/146] CI: Update actions to use environment variables, pin actions (#104610) * CI: use env var in backport.yml * pin actions * pin actions --- .github/workflows/auto-milestone.yml | 2 +- .github/workflows/backport.yml | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/auto-milestone.yml b/.github/workflows/auto-milestone.yml index 0697888fba0..77daec36bca 100644 --- a/.github/workflows/auto-milestone.yml +++ b/.github/workflows/auto-milestone.yml @@ -21,7 +21,7 @@ jobs: # Note: Github will not trigger other actions from this because it uses # the GITHUB_TOKEN token - name: Run auto-milestone - uses: grafana/grafana-github-actions-go/auto-milestone@main + uses: grafana/grafana-github-actions-go/auto-milestone@d4c452f92ed826d515dccf1f62923e537953acd8 # main with: pr: ${{ github.event.pull_request.number }} token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 5f21ab276ee..fca35029cd6 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -11,7 +11,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + with: + persist-credentials: false - name: "Generate token" id: generate_token uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 @@ -20,8 +22,12 @@ jobs: private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} - run: git config --global user.email '132647405+grafana-delivery-bot[bot]@users.noreply.github.com' - run: git config --global user.name 'grafana-delivery-bot[bot]' - - run: git remote set-url origin "https://grafana-delivery-bot:${{ steps.generate_token.outputs.token }}@github.com/grafana/grafana.git" + - name: Set remote URL + env: + GIT_TOKEN: ${{ steps.generate_token.outputs.token }} + run: | + git remote set-url origin "https://grafana-delivery-bot:$GIT_TOKEN@github.com/grafana/grafana.git" - name: Run backport - uses: grafana/grafana-github-actions-go/backport@main + uses: grafana/grafana-github-actions-go/backport@d4c452f92ed826d515dccf1f62923e537953acd8 # main with: token: ${{ steps.generate_token.outputs.token }} From a8ea72012bde926b7c2be338aa7793a346253926 Mon Sep 17 00:00:00 2001 From: Sven Grossmann Date: Mon, 28 Apr 2025 13:00:29 +0200 Subject: [PATCH 146/146] ci: remove unused workflow (#104615) Delete epic-add-to-platform-ux-parent-project.yml This is an old workflow which is no longer used. Co-authored-by: Mihaela Maior --- ...epic-add-to-platform-ux-parent-project.yml | 149 ------------------ 1 file changed, 149 deletions(-) delete mode 100644 .github/workflows/epic-add-to-platform-ux-parent-project.yml diff --git a/.github/workflows/epic-add-to-platform-ux-parent-project.yml b/.github/workflows/epic-add-to-platform-ux-parent-project.yml deleted file mode 100644 index 97462269fce..00000000000 --- a/.github/workflows/epic-add-to-platform-ux-parent-project.yml +++ /dev/null @@ -1,149 +0,0 @@ -name: When epic issues changed in Platform UX squad projects, check if epic is part of specified child projects and update on Platform UX parent project - -on: - issues: - types: [opened, closed, edited, reopened, assigned, unassigned, labeled, unlabeled] - labels: - - 'type/epic' - -env: - GH_TOKEN: ${{ secrets.GH_BOT_PROJECTS_ACCESS_TOKEN }} - ORGANIZATION: ${{ github.repository_owner }} - REPO: ${{ github.event.repository.name }} - PARENT_PROJECT: 304 - CHILD_PROJECT_1: 78 - CHILD_PROJECT_2: 111 - CHILD_PROJECT_3: 202 - -concurrency: - group: issue-add-to-parent-project-${{ github.event.number }} -jobs: - config: - runs-on: "ubuntu-latest" - outputs: - has-secrets: ${{ steps.check.outputs.has-secrets }} - steps: - - name: "Check for secrets" - id: check - shell: bash - run: | - if [ -n "${{ (secrets.GH_BOT_PROJECTS_ACCESS_TOKEN != '') || '' }}" ]; then - echo "has-secrets=1" >> "$GITHUB_OUTPUT" - fi - - main: - needs: config - if: needs.config.outputs.has-secrets && contains(github.event.issue.labels.*.name, 'type/epic') - runs-on: ubuntu-latest - steps: - - name: Check if issue is in child or parent projects - run: | - gh api graphql -f query=' - query($org: String!, $repo: String!) { - repository(name: $repo, owner: $org) { - issue (number: ${{ github.event.issue.number }}) { - projectItems(first:20) { - nodes { - id, - project { - number, - title - }, - fieldValueByName(name:"Status") { - ... on ProjectV2ItemFieldSingleSelectValue { - optionId - name - } - } - } - } - } - } - }' -f org=$ORGANIZATION -f repo=$REPO > projects_data.json - - echo 'IN_PARENT_PROJ='$(jq '.data.repository.issue.projectItems.nodes[] | select(.project.number==${{ env.PARENT_PROJECT }}) | .project != null' projects_data.json) >> $GITHUB_ENV - echo 'PARENT_PROJ_STATUS_ID='$(jq '.data.repository.issue.projectItems.nodes[] | select(.project.number==${{ env.PARENT_PROJECT }}) | select(.fieldValueByName != null) | .fieldValueByName.optionId' projects_data.json) >> $GITHUB_ENV - echo 'ITEM_ID='$(jq '.data.repository.issue.projectItems.nodes[] | select(.project.number==${{ env.PARENT_PROJECT }}) | .id' projects_data.json) >> $GITHUB_ENV - echo 'IN_CHILD_PROJ='$(jq 'first(.data.repository.issue.projectItems.nodes[] | select(.project.number==${{ env.CHILD_PROJECT_1 }} or .project.number==${{ env.CHILD_PROJECT_2 }} or .project.number==${{ env.CHILD_PROJECT_3 }}) | .project != null)' projects_data.json) >> $GITHUB_ENV - echo 'CHILD_PROJ_STATUS='$(jq -r '.data.repository.issue.projectItems.nodes[] | select(.project.number==${{ env.CHILD_PROJECT_1 }} or .project.number==${{ env.CHILD_PROJECT_2 }} or .project.number==${{ env.CHILD_PROJECT_3 }}) | select(.fieldValueByName != null) | .fieldValueByName.name' projects_data.json) >> $GITHUB_ENV - - name: Get parent project project data - if: env.IN_CHILD_PROJ - run: | - gh api graphql -f query=' - query($org: String!, $number: Int!) { - organization(login: $org){ - projectV2(number: $number) { - id - fields(first:20) { - nodes { - ... on ProjectV2Field { - id - name - } - ... on ProjectV2SingleSelectField { - id - name - options { - id - name - } - } - } - } - } - } - }' -f org=$ORGANIZATION -F number=$PARENT_PROJECT > project_data.json - - echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV - echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV - echo 'TODO_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name=="Todo") |.id' project_data.json) >> $GITHUB_ENV - echo 'PROGRESS_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name=="In Progress") |.id' project_data.json) >> $GITHUB_ENV - echo 'DONE_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name=="Done") |.id' project_data.json) >> $GITHUB_ENV - - name: Add issue to parent project - if: env.IN_CHILD_PROJ && !env.IN_PARENT_PROJ - run: | - item_id="$( gh api graphql -f query=' - mutation($project:ID!, $issue:ID!) { - addProjectV2ItemById(input: {projectId: $project, contentId: $issue}) { - item { - id - } - } - }' -f project=$PROJECT_ID -f issue=${{ github.event.issue.node_id }} --jq '.data.addProjectV2ItemById.item.id')" - - echo 'ITEM_ID='$item_id >> $GITHUB_ENV - - name: Set parent project status Done - if: contains(env.CHILD_PROJ_STATUS, 'Done') - run: | - echo 'OPTION_ID='$DONE_OPTION_ID >> $GITHUB_ENV - - name: Set parent project status In Progress - if: contains(env.CHILD_PROJ_STATUS, 'In ') || contains(env.CHILD_PROJ_STATUS, 'Blocked') - run: | - echo 'OPTION_ID='$PROGRESS_OPTION_ID >> $GITHUB_ENV - - name: Set parent project status To do - if: env.CHILD_PROJ_STATUS && !contains(env.CHILD_PROJ_STATUS, 'In ') && !contains(env.CHILD_PROJ_STATUS, 'Blocked') && ! contains(env.CHILD_PROJ_STATUS, 'Done') - run: | - echo 'OPTION_ID='$TODO_OPTION_ID >> $GITHUB_ENV - - name: Set issue status in parent project - if: env.OPTION_ID && (env.OPTION_ID != env.PARENT_PROJ_STATUS_ID) - run: | - gh api graphql -f query=' - mutation ( - $project: ID! - $item: ID! - $status_field: ID! - $status_value: String! - ) { - set_status: updateProjectV2ItemFieldValue(input: { - projectId: $project - itemId: $item - fieldId: $status_field - value: { - singleSelectOptionId: $status_value - } - }) { - projectV2Item { - id - } - } - }' -f project=$PROJECT_ID -f item=$ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.OPTION_ID }} --silent