diff --git a/.betterer.results b/.betterer.results index f83e79923d8..5aa32246134 100644 --- a/.betterer.results +++ b/.betterer.results @@ -6712,10 +6712,6 @@ exports[`better eslint`] = { "public/app/plugins/panel/timeseries/SpanNullsEditor.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], - "public/app/plugins/panel/timeseries/TimezonesEditor.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"] - ], "public/app/plugins/panel/timeseries/migrations.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index aea94e92eeb..d9c88232b09 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -731,7 +731,6 @@ embed.go @grafana/grafana-as-code /.github/workflows/remove-milestone.yml @grafana/grafana-release-guild /.github/workflows/sbom-report.yml @grafana/security-team /.github/workflows/scripts/json-file-to-job-output.js @grafana/plugins-platform-frontend -/.github/workflows/scripts/pr-get-job-link.js @grafana/plugins-platform-frontend /.github/workflows/stale.yml @grafana/grafana-release-guild /.github/workflows/update-changelog.yml @grafana/grafana-release-guild /.github/workflows/update-make-docs.yml @grafana/docs-tooling diff --git a/.github/commands.json b/.github/commands.json index af047be4341..cbbdea3813b 100644 --- a/.github/commands.json +++ b/.github/commands.json @@ -59,14 +59,6 @@ "url": "https://github.com/orgs/grafana/projects/76" } }, - { - "type": "label", - "name": "type/docs", - "action": "addToProject", - "addToProject": { - "url": "https://github.com/orgs/grafana/projects/69" - } - }, { "type": "label", "name": "datasource/Azure", diff --git a/.github/workflows/detect-breaking-changes-levitate.yml b/.github/workflows/detect-breaking-changes-levitate.yml index 8e802abd639..bb71dc4cfe8 100644 --- a/.github/workflows/detect-breaking-changes-levitate.yml +++ b/.github/workflows/detect-breaking-changes-levitate.yml @@ -6,6 +6,10 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +permissions: + contents: read + id-token: write + on: pull_request: paths: @@ -154,26 +158,16 @@ jobs: project_id: 'grafanalabs-global' install_components: 'bq' - - name: Get link for the Github Action job - id: job - uses: actions/github-script@v6 - with: - script: | - const name = 'Detect breaking changes'; - const script = require('./.github/workflows/scripts/pr-get-job-link.js') - await script({name, github, context, core}) - - name: Detect breaking changes id: breaking-changes run: ./scripts/check-breaking-changes.sh env: FORCE_COLOR: 3 - GITHUB_JOB_LINK: ${{ steps.job.outputs.link }} - name: Persisting the check output run: | mkdir -p ./levitate - echo "{ \"exit_code\": ${{ steps.breaking-changes.outputs.is_breaking }}, \"message\": \"${{ steps.breaking-changes.outputs.message }}\", \"job_link\": \"${{ steps.job.outputs.link }}#step:${GITHUB_STEP_NUMBER}:1\", \"pr_number\": \"${{ github.event.pull_request.number }}\" }" > ./levitate/result.json + echo "{ \"exit_code\": ${{ steps.breaking-changes.outputs.is_breaking }}, \"message\": \"${{ steps.breaking-changes.outputs.message }}\", \"pr_number\": \"${{ github.event.pull_request.number }}\" }" > ./levitate/result.json - name: Upload check output as artifact uses: actions/upload-artifact@v4 @@ -219,15 +213,12 @@ jobs: PR_NUMBER: ${{ github.event.pull_request.number }} with: script: | - const { data } = await github.rest.issues.listLabelsOnIssue({ - issue_number: process.env.PR_NUMBER, + const { data: labels } = await github.rest.issues.listLabelsOnIssue({ + issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, }); - const labels = data.map(({ name }) => name); - const doesExist = labels.includes('levitate breaking change'); - - return doesExist ? 1 : 0; + return labels.some(label => label.name === 'levitate breaking change') ? 1 : 0 # put the markdown into a variable - name: Levitate Markdown @@ -271,22 +262,41 @@ jobs: delete: true GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} - # Posts a notification to Slack if a PR has a breaking change and it did not have a breaking change before - - name: Post to Slack + - name: Send Slack Message via Payload id: slack - if: steps.levitate-run.outputs.exit_code == 1 && steps.does-label-exist.outputs.result == 0 && env.HAS_SECRETS - uses: slackapi/slack-github-action@v1.26.0 + if: steps.levitate-run.outputs.exit_code == 1 && steps.does-label-exist.outputs.result == 0 && github.repository == 'grafana/grafana' + uses: grafana/shared-workflows/actions/send-slack-message@main with: - payload: | + channel-id: "C031SLFH6G0" + payload: | { - "pr_link": "https://github.com/grafana/grafana/pull/${{ steps.levitate-run.outputs.pr_number }}", - "pr_number": "${{ steps.levitate-run.outputs.pr_number }}", - "job_link": "${{ steps.levitate-run.outputs.job_link }}", - "message": "${{ steps.levitate-run.outputs.message }}" + "channel": "C031SLFH6G0", + "text": ":warning: Possible breaking changes detected in *PR:* <${{ github.event.pull_request.html_url }}|#${{ github.event.pull_request.number }} :warning:", + "icon_emoji": ":grot:", + "username": "Levitate Bot", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*grafana/grafana* repository has possible breaking changes" + } + }, + { + "type": "section", + "fields": [ + { + "type": "mrkdwn", + "text": "*PR:* <${{ github.event.pull_request.html_url }}|#${{ github.event.pull_request.number }}>" + }, + { + "type": "mrkdwn", + "text": "*Job:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Job>" + } + ] + } + ] } - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_LEVITATE_WEBHOOK_URL }} - HAS_SECRETS: ${{ (github.repository == 'grafana/grafana' || secrets.SLACK_LEVITATE_WEBHOOK_URL != '') || '' }} # Add the label - name: Add "levitate breaking change" label diff --git a/.github/workflows/scripts/pr-get-job-link.js b/.github/workflows/scripts/pr-get-job-link.js deleted file mode 100644 index b1b49c89941..00000000000 --- a/.github/workflows/scripts/pr-get-job-link.js +++ /dev/null @@ -1,9 +0,0 @@ - -module.exports = async ({ name, github, context, core }) => { - const { owner, repo } = context.repo; - const url = `https://api.github.com/repos/${owner}/${repo}/actions/runs/${context.runId}/jobs` - const result = await github.request(url); - const job = result.data.jobs.find(j => j.name === name); - - core.setOutput('link', `${job.html_url}?check_suite_focus=true`); -} diff --git a/Makefile b/Makefile index 9346e4b9b10..9c8f178a3d9 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,7 @@ GO_RACE_FLAG := $(if $(GO_RACE),-race) GO_BUILD_FLAGS += $(if $(GO_BUILD_DEV),-dev) GO_BUILD_FLAGS += $(if $(GO_BUILD_TAGS),-build-tags=$(GO_BUILD_TAGS)) GO_BUILD_FLAGS += $(GO_RACE_FLAG) +GIT_BASE = remotes/origin/main # GNU xargs has flag -r, and BSD xargs (e.g. MacOS) has that behaviour by default XARGSR = $(shell xargs --version 2>&1 | grep -q GNU && echo xargs -r || echo xargs) @@ -308,7 +309,7 @@ lint-go: golangci-lint ## Run all code checks for backend. You can use GO_LINT_F .PHONY: lint-go-diff lint-go-diff: $(GOLANGCI_LINT) - git diff --name-only remotes/origin/main | \ + git diff --name-only $(GIT_BASE) | \ grep '\.go$$' | \ $(XARGSR) dirname | \ sort -u | \ diff --git a/docs/sources/dashboards/create-reports/index.md b/docs/sources/dashboards/create-reports/index.md index 29beabb4794..272b948d796 100644 --- a/docs/sources/dashboards/create-reports/index.md +++ b/docs/sources/dashboards/create-reports/index.md @@ -39,6 +39,11 @@ refs: destination: /docs/grafana//administration/roles-and-permissions/access-control/ - pattern: /docs/grafana-cloud/ destination: /docs/grafana//administration/roles-and-permissions/access-control/ + permission: + - pattern: /docs/grafana/ + destination: /docs/grafana//administration/roles-and-permissions/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//administration/roles-and-permissions/ role-based-access-control: - pattern: /docs/grafana/ destination: /docs/grafana//administration/roles-and-permissions/access-control/ @@ -109,7 +114,7 @@ For information about recent improvements to the reporting UI, refer to [Grafana ## Access control -When [RBAC](ref:rbac) is enabled, you need to have the relevant [Permissions][] to create and manage reports. +When [RBAC](ref:rbac) is enabled, you need to have the relevant [Permissions](ref:permission) to create and manage reports. ## Create or update a report 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 ef8bc6a3396..46c13858bb3 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -191,11 +191,10 @@ Experimental features might be changed or removed without prior notice. | `queryLibrary` | Enables Query Library feature in Explore | | `logsExploreTableDefaultVisualization` | Sets the logs table as default visualisation in logs explore | | `alertingListViewV2` | Enables the new alert list view design | -| `dashboardRestore` | Enables deleted dashboard restore feature (backend only) | +| `dashboardRestore` | Enables deleted dashboard restore feature | | `alertingCentralAlertHistory` | Enables the new central alert history. | | `failWrongDSUID` | Throws an error if a datasource has an invalid UIDs | | `alertingApiServer` | Register Alerting APIs with the K8s API server | -| `dashboardRestoreUI` | Enables the frontend to be able to restore a recently deleted dashboard | | `dataplaneAggregator` | Enable grafana dataplane aggregator | | `newFiltersUI` | Enables new combobox style UI for the Ad hoc filters variable in scenes architecture | | `lokiSendDashboardPanelNames` | Send dashboard and panel names to Loki when querying | diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/saml/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/saml/index.md index 29ef9f04975..11b405c1959 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/saml/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/saml/index.md @@ -231,21 +231,34 @@ This app registration will be used as a Service Account to retrieve more informa 1. Go to the [Azure portal](https://portal.azure.com/#home) and sign in with your Azure AD account. 1. In the left-hand navigation pane, select the Azure Active Directory service, and then select **App registrations**. -1. Select **New registration**. +1. Click the **New registration** button. 1. In the **Register an application** pane, enter a name for the application. 1. In the **Supported account types** section, select the account types that can use the application. 1. In the **Redirect URI** section, select Web and enter `https://localhost/login/azuread`. -1. Select **Register**. +1. Click the **Register** button. #### Set up permissions for the application 1. In the overview pane, look for **API permissions** section and select **Add a permission**. 1. In the **Request API permissions** pane, select **Microsoft Graph**, and click **Application permissions**. 1. In the **Select permissions** pane, under the **GroupMember** section, select **GroupMember.Read.All**. +1. In the **Select permissions** pane, under the **User** section, select **User.Read.All**. +1. Click the **Add permissions** button at the bottom of the page. +1. In the **Request API permissions** pane, select **Microsoft Graph**, and click **Delegated permissions**. 1. In the **Select permissions** pane, under the **User** section, select **User.Read**. -1. Select **Add permissions** at the bottom of the page. +1. Click the **Add permissions** button at the bottom of the page. 1. In the **API permissions** section, select **Grant admin consent for **. +The following table shows what the permissions look like from the Azure AD portal: + +| Permissions name | Type | Admin consent required | Status | +| ---------------- | ----------- | ---------------------- | ------- | +| `Group.Read.All` | Application | Yes | Granted | +| `User.Read` | Delegated | No | Granted | +| `User.Read.All` | Application | Yes | Granted | + +{{< figure src="/media/docs/grafana/saml/graph-api-app-permissions.png" caption="Screen shot of the permissions listed in Azure AD for the App registration" >}} + #### Generate a client secret 1. In the **Overview** pane, select **Certificates & secrets**. diff --git a/docs/sources/setup-grafana/installation/helm/index.md b/docs/sources/setup-grafana/installation/helm/index.md index 566f73d5610..d34e5c35f0e 100644 --- a/docs/sources/setup-grafana/installation/helm/index.md +++ b/docs/sources/setup-grafana/installation/helm/index.md @@ -278,6 +278,67 @@ To install plugins in the Grafana Helm Charts, complete the following steps: 1. Search for the above plugins and they should be marked as installed. +### Configure a Private CA (Certificate Authority) + +In many enterprise networks, TLS certificates are issued by a private certificate authority and are not trusted by default (using the provided OS trust chain). + +If your Grafana instance needs to interact with services exposing certificates issued by these private CAs, then you need to ensure Grafana trusts the root certificate. + +You might need to configure this if you: + +- have plugins that require connectivity to other self hosted systems. For example, if you've installed the Grafana Enterprise Metrics, Logs, or Traces (GEM, GEL, GET) plugins, and your GEM (or GEL/GET) cluster is using a private certificate. +- want to connect to data sources which are listening on HTTPS with a private certificate. +- are using a backend database for persistence, or caching service that uses private certificates for encryption in transit. + +In some cases you can specify a self-signed certificate within Grafana (such as in some data sources), or choose to skip TLS certificate validation (this is not recommended unless absolutely necessary). + +A simple solution which should work across your entire instance (plugins, data sources, and backend connections) is to add your self-signed CA certificate to your Kubernetes deployment. + +1. Create a ConfigMap containing the certificate, and deploy it to your Kubernetes cluster + + ```yaml + # grafana-ca-configmap.yaml + --- + apiVersion: v1 + kind: ConfigMap + metadata: + name: grafana-ca-cert + data: + ca.pem: | + -----BEGIN CERTIFICATE----- + (rest of the CA cert) + -----END CERTIFICATE----- + ``` + + ```bash + kubectl apply --filename grafana-ca-configmap.yaml --namespace monitoring + ``` + +1. Open the Helm `values.yaml` file in your favorite editor. + +1. Find the line that says `extraConfigmapMounts:` and under that section, specify the additional ConfigMap that you want to mount. + + ```yaml + ....... + ............ + ...... + extraConfigmapMounts: + - name: ca-certs-configmap + mountPath: /etc/ssl/certs/ca.pem + subPath: ca.pem + configMap: grafana-ca-cert + readOnly: true + ....... + ............ + ...... + ``` + +1. Save the changes and use the `helm upgrade` command to update your Grafana deployment and mount the new ConfigMap: + + ```bash + helm upgrade my-grafana grafana/grafana --values values.yaml --namespace monitoring + ``` + ## Troubleshooting This section includes troubleshooting tips you might find helpful when deploying Grafana on Kubernetes via Helm. diff --git a/docs/sources/upgrade-guide/_index.md b/docs/sources/upgrade-guide/_index.md index 390c81e965c..874374740f4 100644 --- a/docs/sources/upgrade-guide/_index.md +++ b/docs/sources/upgrade-guide/_index.md @@ -22,7 +22,7 @@ We recommend that you upgrade Grafana often to stay current with the latest fixe Because Grafana upgrades are backward compatible, the upgrade process is straightforward, and dashboards and graphs will not change. -To learn what's available in a Grafana release, refer to [What's New ]({{< relref "../whatsnew" >}}). +To learn what's available in a Grafana release, refer to [What's New]({{< relref "../whatsnew" >}}). Refer to any of the following upgrade guides: diff --git a/e2e/dashboards-suite/dashboard-share-externally-create.spec.ts b/e2e/dashboards-suite/dashboard-share-externally-create.spec.ts index 39e01c4e60f..6548198013d 100644 --- a/e2e/dashboards-suite/dashboard-share-externally-create.spec.ts +++ b/e2e/dashboards-suite/dashboard-share-externally-create.spec.ts @@ -22,7 +22,9 @@ describe('Shared dashboards', () => { e2e.pages.ShareDashboardDrawer.ShareExternally.container().should('not.exist'); }); - it('Create a shared dashboard and check API', () => { + // Skipping due to being a flaky test + // https://drone.grafana.net/grafana/grafana/201217/6/14 + it.skip('Create a shared dashboard and check API', () => { openDashboard(); // Open share externally drawer @@ -111,7 +113,7 @@ describe('Shared dashboards', () => { }); }); - it('Disable a shared dashboard', () => { + it.skip('Disable a shared dashboard', () => { openDashboard(); //TODO Failing in CI/CD. Fix it diff --git a/go.mod b/go.mod index 7d9993fcdc4..5ab6b92c69d 100644 --- a/go.mod +++ b/go.mod @@ -27,11 +27,10 @@ require ( github.com/Masterminds/semver v1.5.0 // @grafana/grafana-backend-group github.com/Masterminds/semver/v3 v3.2.0 // @grafana/grafana-release-guild github.com/Masterminds/sprig/v3 v3.2.3 // @grafana/grafana-backend-group - github.com/Masterminds/squirrel v1.5.4 // @grafana/identity-access-team github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // @grafana/plugins-platform-backend github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f // @grafana/grafana-backend-group github.com/alicebob/miniredis/v2 v2.33.0 // @grafana/alerting-backend - github.com/andybalholm/brotli v1.0.6 // @grafana/partner-datasources + github.com/andybalholm/brotli v1.1.0 // @grafana/partner-datasources github.com/apache/arrow/go/v15 v15.0.2 // @grafana/observability-metrics github.com/armon/go-radix v1.0.0 // @grafana/grafana-app-platform-squad github.com/aws/aws-sdk-go v1.55.5 // @grafana/aws-datasources @@ -73,7 +72,7 @@ require ( github.com/googleapis/gax-go/v2 v2.13.0 // @grafana/grafana-backend-group github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group github.com/gorilla/websocket v1.5.0 // @grafana/grafana-app-platform-squad - github.com/grafana/alerting v0.0.0-20240930154843-22cee00b280e // @grafana/alerting-backend + github.com/grafana/alerting v0.0.0-20241010165806-807ddf183724 // @grafana/alerting-backend github.com/grafana/authlib v0.0.0-20240919120951-58259833c564 // @grafana/identity-access-team github.com/grafana/authlib/claims v0.0.0-20240827210201-19d5347dd8dd // @grafana/identity-access-team github.com/grafana/codejen v0.0.3 // @grafana/dataviz-squad @@ -123,21 +122,20 @@ require ( github.com/mattn/go-isatty v0.0.20 // @grafana/grafana-backend-group github.com/mattn/go-sqlite3 v1.14.22 // @grafana/grafana-backend-group github.com/matttproud/golang_protobuf_extensions v1.0.4 // @grafana/alerting-backend - github.com/microsoft/go-mssqldb v1.7.0 // @grafana/grafana-bi-squad + github.com/microsoft/go-mssqldb v1.7.2 // @grafana/grafana-bi-squad github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c //@grafana/identity-access-team github.com/mocktools/go-smtp-mock/v2 v2.3.1 // @grafana/grafana-backend-group github.com/modern-go/reflect2 v1.0.2 // @grafana/alerting-backend github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // @grafana/alerting-backend github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // @grafana/grafana-operator-experience-squad github.com/oapi-codegen/oapi-codegen/v2 v2.3.0 // @grafana/grafana-as-code - github.com/oklog/ulid/v2 v2.1.0 // @grafana/identity-access-team github.com/olekukonko/tablewriter v0.0.5 // @grafana/grafana-backend-group - github.com/openfga/api/proto v0.0.0-20240529184453-5b0b4941f3e0 // @grafana/identity-access-team - github.com/openfga/language/pkg/go v0.0.0-20240409225820-a53ea2892d6d // @grafana/identity-access-team - github.com/openfga/openfga v1.5.4 // @grafana/identity-access-team + github.com/openfga/api/proto v0.0.0-20240906203051-102620ef2a66 // @grafana/identity-access-team + github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20240926131254-992b301a003f // @grafana/identity-access-team + github.com/openfga/openfga v1.6.2 // @grafana/identity-access-team github.com/patrickmn/go-cache v2.1.0+incompatible // @grafana/alerting-backend github.com/prometheus/alertmanager v0.27.0 // @grafana/alerting-backend - github.com/prometheus/client_golang v1.20.3 // @grafana/alerting-backend + github.com/prometheus/client_golang v1.20.4 // @grafana/alerting-backend github.com/prometheus/client_model v0.6.1 // @grafana/grafana-backend-group github.com/prometheus/common v0.55.0 // @grafana/alerting-backend github.com/prometheus/prometheus v1.8.2-0.20221021121301-51a44e6657c3 // @grafana/alerting-backend @@ -158,21 +156,21 @@ require ( github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // @grafana/grafana-operator-experience-squad github.com/yudai/gojsondiff v1.0.0 // @grafana/grafana-backend-group go.opentelemetry.io/collector/pdata v1.6.0 // @grafana/grafana-backend-group - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // @grafana/plugins-platform-backend + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 // @grafana/plugins-platform-backend go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.55.0 // @grafana/grafana-operator-experience-squad go.opentelemetry.io/contrib/propagators/jaeger v1.29.0 // @grafana/grafana-backend-group go.opentelemetry.io/contrib/samplers/jaegerremote v0.23.0 // @grafana/grafana-backend-group go.opentelemetry.io/otel v1.30.0 // @grafana/grafana-backend-group go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel/sdk v1.29.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel/sdk v1.30.0 // @grafana/grafana-backend-group go.opentelemetry.io/otel/trace v1.30.0 // @grafana/grafana-backend-group go.uber.org/atomic v1.11.0 // @grafana/alerting-backend go.uber.org/goleak v1.3.0 // @grafana/grafana-search-and-storage gocloud.dev v0.39.0 // @grafana/grafana-app-platform-squad golang.org/x/crypto v0.27.0 // @grafana/grafana-backend-group - golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // @grafana/alerting-backend + golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // @grafana/alerting-backend golang.org/x/mod v0.20.0 // indirect; @grafana/grafana-backend-group golang.org/x/net v0.29.0 // @grafana/oss-big-tent @grafana/partner-datasources golang.org/x/oauth2 v0.23.0 // @grafana/identity-access-team @@ -180,9 +178,9 @@ require ( golang.org/x/text v0.18.0 // @grafana/grafana-backend-group golang.org/x/time v0.6.0 // @grafana/grafana-backend-group golang.org/x/tools v0.24.0 // @grafana/grafana-as-code - gonum.org/v1/gonum v0.14.0 // @grafana/observability-metrics + gonum.org/v1/gonum v0.15.1 // @grafana/observability-metrics google.golang.org/api v0.191.0 // @grafana/grafana-backend-group - google.golang.org/grpc v1.66.0 // @grafana/plugins-platform-backend + google.golang.org/grpc v1.67.0 // @grafana/plugins-platform-backend google.golang.org/protobuf v1.34.2 // @grafana/plugins-platform-backend gopkg.in/ini.v1 v1.67.0 // @grafana/alerting-backend gopkg.in/mail.v2 v2.3.1 // @grafana/grafana-backend-group @@ -224,12 +222,13 @@ require ( github.com/FZambia/eagle v0.1.0 // indirect github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c // indirect github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/squirrel v1.5.4 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/RoaringBitmap/roaring v1.9.3 // indirect github.com/agext/levenshtein v1.2.1 // indirect github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect - github.com/antlr4-go/antlr/v4 v4.13.0 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/apache/thrift v0.20.0 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect @@ -269,7 +268,7 @@ require ( github.com/elazarl/goproxy v0.0.0-20240726154733-8b0c20506380 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/emicklei/proto v1.10.0 // indirect - github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect + github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect @@ -293,7 +292,7 @@ require ( github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/btree v1.1.2 // indirect - github.com/google/cel-go v0.20.1 // indirect + github.com/google/cel-go v0.21.0 // indirect github.com/google/flatbuffers v24.3.25+incompatible // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/gofuzz v1.2.0 // indirect @@ -306,10 +305,8 @@ require ( github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect; @grafana/plugins-platform-backend github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // @grafana/identity-access-team github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-msgpack v0.5.5 // indirect - github.com/hashicorp/go-retryablehttp v0.7.5 // indirect github.com/hashicorp/go-sockaddr v1.0.6 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect @@ -321,9 +318,9 @@ require ( github.com/invopop/jsonschema v0.12.0 // indirect github.com/invopop/yaml v0.3.1 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect - github.com/jackc/pgx/v5 v5.5.5 // indirect - github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.7.1 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect github.com/jcmturner/gofork v1.7.6 // indirect @@ -377,15 +374,16 @@ require ( github.com/oapi-codegen/runtime v1.1.1 // indirect github.com/oklog/run v1.1.0 // indirect github.com/oklog/ulid v1.3.1 // indirect + github.com/oklog/ulid/v2 v2.1.0 // indirect github.com/opentracing-contrib/go-stdlib v1.0.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect - github.com/pelletier/go-toml/v2 v2.1.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/pressly/goose/v3 v3.20.0 // indirect + github.com/pressly/goose/v3 v3.22.1 // indirect github.com/prometheus/common/sigv4 v0.1.0 // indirect github.com/prometheus/exporter-toolkit v0.11.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect @@ -401,14 +399,14 @@ require ( github.com/segmentio/asm v1.2.0 // indirect github.com/segmentio/encoding v0.4.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect - github.com/sethvargo/go-retry v0.2.4 // indirect - github.com/shopspring/decimal v1.3.1 // indirect + github.com/sethvargo/go-retry v0.3.0 // indirect + github.com/shopspring/decimal v1.4.0 // indirect github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect - github.com/spf13/viper v1.18.2 // indirect + github.com/spf13/viper v1.19.0 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect @@ -440,8 +438,8 @@ require ( golang.org/x/term v0.24.0 // indirect golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 // indirect; @grafana/grafana-backend-group - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect @@ -449,10 +447,10 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/kms v0.31.1 // indirect modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect - modernc.org/libc v1.41.0 // indirect + modernc.org/libc v1.55.3 // indirect modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.7.2 // indirect - modernc.org/sqlite v1.29.6 // indirect + modernc.org/memory v1.8.0 // indirect + modernc.org/sqlite v1.33.1 // indirect modernc.org/strutil v1.2.0 // indirect modernc.org/token v1.1.0 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 // indirect @@ -484,6 +482,7 @@ require ( cloud.google.com/go/longrunning v0.5.12 // indirect github.com/at-wat/mqtt-go v0.19.4 // indirect github.com/dolthub/maphash v0.1.0 // indirect + github.com/emirpasic/gods v1.18.1 // indirect github.com/gammazero/deque v0.2.1 // indirect github.com/grafana/grafana-app-sdk v0.19.0 // indirect github.com/grafana/grafana/pkg/semconv v0.0.0-20240808213237-f4d2e064f435 // indirect diff --git a/go.sum b/go.sum index 58deaa35524..f707fdc6534 100644 --- a/go.sum +++ b/go.sum @@ -1334,8 +1334,6 @@ cloud.google.com/go/workflows v1.12.1/go.mod h1:5A95OhD/edtOhQd/O741NSfIMezNTbCw cloud.google.com/go/workflows v1.12.2/go.mod h1:+OmBIgNqYJPVggnMo9nqmizW0qEXHhmnAzK/CnBqsHc= cloud.google.com/go/workflows v1.12.3/go.mod h1:fmOUeeqEwPzIU81foMjTRQIdwQHADi/vEr1cx9R1m5g= cloud.google.com/go/workflows v1.12.4/go.mod h1:yQ7HUqOkdJK4duVtMeBCAOPiN1ZF1E9pAMX51vpwB/w= -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/age v1.1.1 h1:pIpO7l151hCnQ4BdyBujnGP2YlUo0uj6sAVNHGBvXHg= filippo.io/age v1.1.1/go.mod h1:l03SrzDUrBkdBx8+IILdnn2KZysqQdbEBUQ4p3sqEQE= @@ -1389,8 +1387,6 @@ github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7Og github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw= @@ -1453,8 +1449,6 @@ github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA4 github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= @@ -1502,11 +1496,11 @@ github.com/alicebob/miniredis/v2 v2.33.0 h1:uvTF0EDeu9RLnUEG27Db5I68ESoIxTiXbNUi github.com/alicebob/miniredis/v2 v2.33.0/go.mod h1:MhP4a3EU7aENRi9aO+tHfTBZicLqQevyi/DJpoj6mi0= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= -github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= -github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/apache/arrow/go/arrow v0.0.0-20210223225224-5bea62493d91/go.mod h1:c9sxoIT3YgLxH4UhLOCKaBlEojuMhVYpk4Ntv3opUTQ= github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= @@ -1723,8 +1717,8 @@ github.com/cncf/xds/go v0.0.0-20230428030218-4003588d1b74/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= -github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw= -github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 h1:N+3sFI5GUjRKBi+i0TxYVST9h4Ie192jJWpHvthBBgg= +github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= @@ -1734,9 +1728,6 @@ github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE github.com/containerd/cgroups/v3 v3.0.1/go.mod h1:/vtwk1VXrtoa5AaZLkypuOJgA/6DyPMZHJPGQNtlHnw= github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= github.com/containerd/containerd v1.2.7/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.7.12 h1:+KQsnv4VnzyxWcfO9mlxxELaoztsDEjOuCMPAuPqgU0= -github.com/containerd/containerd v1.7.12/go.mod h1:/5OMpE1p0ylxtEUGY8kuCYkDRzJm9NO1TFMWjUpdevk= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= @@ -1749,8 +1740,6 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV 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/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 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.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= @@ -1805,8 +1794,8 @@ github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4Kfc github.com/docker/docker v0.7.3-0.20190103212154-2b7e084dc98b/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v0.7.3-0.20190817195342-4760db040282/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v26.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v26.0.2+incompatible h1:yGVmKUFGgcxA6PXWAokO0sQL22BrQ67cgVjko8tGdXE= -github.com/docker/docker v26.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= +github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= @@ -1839,6 +1828,8 @@ github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxER github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/proto v1.10.0 h1:pDGyFRVV5RvV+nkBK9iy3q67FBy9Xa7vwrOTE+g5aGw= github.com/emicklei/proto v1.10.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -1854,8 +1845,8 @@ github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCw github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g= github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0= -github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155 h1:IgJPqnrlY2Mr4pYB6oaMKvFvwJ9H+X6CCY5x1vCTcpc= -github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155/go.mod h1:5Wkq+JduFtdAXihLmeTJf+tRYIT4KBc2vPXDhwVo1pA= +github.com/envoyproxy/go-control-plane v0.13.0 h1:HzkeUz1Knt+3bK+8LG1bxOO/jzWZmdxpwC51i202les= +github.com/envoyproxy/go-control-plane v0.13.0/go.mod h1:GRaKG3dwvFoTg4nj7aXdZnvMg4d7nvT/wl9WgVXn3Q8= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= @@ -1863,8 +1854,9 @@ github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6Ni github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/envoyproxy/protoc-gen-validate v1.0.1/go.mod h1:0vj8bNkYbSTNS2PIyH87KZaeN4x9zpL9Qt8fQC7d+vs= github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= -github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= +github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= +github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:IT4JYU7k4ikYg1SCxNI1/Tieq/NFvh6dzLdgi7eu0tM= @@ -1953,8 +1945,6 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/analysis v0.21.5/go.mod h1:25YcZosX9Lwz2wBsrFrrsL8bmjjXdlyP6zsr2AMy29M= github.com/go-openapi/analysis v0.22.0/go.mod h1:acDnkkCI2QxIo8sSIPgmp1wUlRohV7vfGtAIVae73b0= github.com/go-openapi/analysis v0.22.2/go.mod h1:pDF4UbZsQTo/oNuRfAWWd4dAh4yuYf//LYorPTjrpvo= @@ -2124,8 +2114,8 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= -github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= +github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= +github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= @@ -2257,8 +2247,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/alerting v0.0.0-20240930154843-22cee00b280e h1:RttFYx5+RTNuMPlaftx8i9f91kwUi9LdxsoPLHnticU= -github.com/grafana/alerting v0.0.0-20240930154843-22cee00b280e/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= +github.com/grafana/alerting v0.0.0-20241010165806-807ddf183724 h1:u+ZM5TLkdeEoSWXgYWxc4XRfPHhXpR63MyHXJxbBLrc= +github.com/grafana/alerting v0.0.0-20241010165806-807ddf183724/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= github.com/grafana/authlib v0.0.0-20240919120951-58259833c564 h1:zYF/RBulpvMqPYR3gbzJZ8t/j/Eymn5FNidSYkueNCA= github.com/grafana/authlib v0.0.0-20240919120951-58259833c564/go.mod h1:PFzXbCrn0GIpN4KwT6NP1l5Z1CPLfmKHnYx8rZzQcyY= github.com/grafana/authlib/claims v0.0.0-20240827210201-19d5347dd8dd h1:sIlR7n38/MnZvX2qxDEszywXdI5soCwQ78aTDSARvus= @@ -2396,8 +2386,8 @@ github.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1 github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= -github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= -github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= @@ -2490,13 +2480,13 @@ github.com/ionos-cloud/sdk-go/v6 v6.1.11/go.mod h1:EzEgRIDxBELvfoa/uBN0kOQaqovLj github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= -github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= -github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= -github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= -github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= -github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= +github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= @@ -2621,8 +2611,6 @@ github.com/linkedin/goavro/v2 v2.10.0 h1:eTBIRoInBM88gITGXYtUSqqxLTFXfOsJBiX8ZMW github.com/linkedin/goavro/v2 v2.10.0/go.mod h1:UgQUb2N/pmueQYH9bfqFioWxzYCZXSfF8Jw03O5sjqA= github.com/linode/linodego v1.32.0 h1:OmZzB3iON6uu84VtLFf64uKmAQqJJarvmsVguroioPI= github.com/linode/linodego v1.32.0/go.mod h1:y8GDP9uLVH4jTB9qyrgw79qfKdYJmNCGUOJmfuiOcmI= -github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c h1:VtwQ41oftZwlMnOEbMWQtSEUgU64U4s+GHk7hZK+jtY= -github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= @@ -2690,8 +2678,8 @@ github.com/maypok86/otter v1.2.2 h1:jJi0y8ruR/ZcKmJ4FbQj3QQTqKwV+LNrSOo2S1zbF5M= github.com/maypok86/otter v1.2.2/go.mod h1:mKLfoI7v1HOmQMwFgX4QkRk23mX6ge3RDvjdHOWG4R4= github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= -github.com/microsoft/go-mssqldb v1.7.0 h1:sgMPW0HA6Ihd37Yx0MzHyKD726C2kY/8KJsQtXHNaAs= -github.com/microsoft/go-mssqldb v1.7.0/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA= +github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA= +github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 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= @@ -2750,18 +2738,10 @@ github.com/mithrandie/ternary v1.1.1 h1:k/joD6UGVYxHixYmSR8EGgDFNONBMqyD373xT4QR github.com/mithrandie/ternary v1.1.1/go.mod h1:0D9Ba3+09K2TdSZO7/bFCC0GjSXetCvYuYq0u8FY/1g= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= -github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8= github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= -github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= -github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= -github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= -github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/mocktools/go-smtp-mock/v2 v2.3.1 h1:wq75NDSsOy5oHo/gEQQT0fRRaYKRqr1IdkjhIPXxagM= github.com/mocktools/go-smtp-mock/v2 v2.3.1/go.mod h1:h9AOf/IXLSU2m/1u4zsjtOM/WddPwdOUBz56dV9f81M= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -2778,7 +2758,6 @@ github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJ github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de h1:D5x39vF5KCwKQaw+OC9ZPiLVHXz3UFw2+psEX+gYcto= github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de/go.mod h1:kJun4WP5gFuHZgRjZUWWuH1DTxCtxbHDOIJsudS8jzY= @@ -2885,12 +2864,12 @@ github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zM github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/openfga/api/proto v0.0.0-20240529184453-5b0b4941f3e0 h1:tSJl/atdqsDACjRQQPCbXe2GfEvcOkhdrUNmDToAjTA= -github.com/openfga/api/proto v0.0.0-20240529184453-5b0b4941f3e0/go.mod h1:XnvYrdU//9i70Aou6n4H5DJ0bdRPB3IlmE/Vx6qhnm8= -github.com/openfga/language/pkg/go v0.0.0-20240409225820-a53ea2892d6d h1:n44DfITs+CLCYJIgsryJkG2ElwOZJ3huekPZKydPi7U= -github.com/openfga/language/pkg/go v0.0.0-20240409225820-a53ea2892d6d/go.mod h1:wkI4GcY3yNNuFMU2ncHPWqBaF7XylQTkJYfBi2pIpK8= -github.com/openfga/openfga v1.5.4 h1:mVrp0uB9jNWX/5+OtZLM6YOx5Y9Y4r/D/O+LNBF/FGQ= -github.com/openfga/openfga v1.5.4/go.mod h1:+PoZg9BJeq+h3L0eR52tqNTwghSapCFtmaVKHsUK7QM= +github.com/openfga/api/proto v0.0.0-20240906203051-102620ef2a66 h1:pAYrdyIKxPsMs/nRcTEnS9za2io11g7Rt7ng7HK82hk= +github.com/openfga/api/proto v0.0.0-20240906203051-102620ef2a66/go.mod h1:gil5LBD8tSdFQbUkCQdnXsoeU9kDJdJgbGdHkgJfcd0= +github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20240926131254-992b301a003f h1:ZMZ7ntMnaHIPZxvVQv/aqC4ctzLqH+9Fqn4uw35kQpk= +github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20240926131254-992b301a003f/go.mod h1:ll/hN6kS4EE6B/7J/PbZqac9Nuv7ZHpI+Jfh36JLrbs= +github.com/openfga/openfga v1.6.2 h1:tHBAgiCPomCZb3IH0CFqOpTDVDA/Xb8kG87oX4JCbXc= +github.com/openfga/openfga v1.6.2/go.mod h1:jzbEpheazf6MFjtanQt1rpxexSRzfa9057F7JlkMv2I= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing-contrib/go-stdlib v1.0.0 h1:TBS7YuVotp8myLon4Pv7BtCBzOTo1DeZCld0Z63mW2w= github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU= @@ -2919,8 +2898,8 @@ github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtP github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= -github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= -github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= @@ -2956,11 +2935,9 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI 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/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c h1:NRoLoZvkBTKvR5gQLgA3e0hqjkY9u1wm+iOL45VN/qI= -github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= -github.com/pressly/goose/v3 v3.20.0 h1:uPJdOxF/Ipj7ABVNOAMJXSxwFXZGwMGHNqjC8e61VA0= -github.com/pressly/goose/v3 v3.20.0/go.mod h1:BRfF2GcG4FTG12QfdBVy3q1yveaf4ckL9vWwEcIO3lA= +github.com/pressly/goose/v3 v3.22.1 h1:2zICEfr1O3yTP9BRZMGPj7qFxQ+ik6yeo+z1LMuioLc= +github.com/pressly/goose/v3 v3.22.1/go.mod h1:xtMpbstWyCpyH+0cxLTMCENWBG+0CSxvTsXhW95d5eo= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= @@ -2977,8 +2954,8 @@ github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= -github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4= -github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= +github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -3093,19 +3070,15 @@ github.com/segmentio/encoding v0.4.0 h1:MEBYvRqiUB2nfR2criEXWqwdY6HJOUrCn5hboVOV github.com/segmentio/encoding v0.4.0/go.mod h1:/d03Cd8PoaDeceuhUUUQWjU0KhWjrmYrWPgtJHYZSnI= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= -github.com/sethvargo/go-retry v0.2.4 h1:T+jHEQy/zKJf5s95UkguisicE0zuF9y7+/vgz08Ocec= -github.com/sethvargo/go-retry v0.2.4/go.mod h1:1afjQuvh7s4gflMObvjLPaWgluLLyhA1wmVZ6KLpICw= +github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= +github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= github.com/shadowspore/fossil-delta v0.0.0-20240102155221-e3a8590b820b h1:SCYeryKXBVdW38167VyumGakH+7E4Wxe6b/zxmQxwyM= github.com/shadowspore/fossil-delta v0.0.0-20240102155221-e3a8590b820b/go.mod h1:daNLfX/GJKuZyN4HkMf0h8dVmTmgRbBSkd9bFQyGNIo= -github.com/shirou/gopsutil/v3 v3.24.2 h1:kcR0erMbLg5/3LcInpw0X/rrPSqq4CDPyI6A6ZRC18Y= -github.com/shirou/gopsutil/v3 v3.24.2/go.mod h1:tSg/594BcA+8UdQU2XcW803GWYgdtauFFPgJCJKZlVk= -github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= -github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v1.7.1/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= -github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs= github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M= @@ -3159,8 +3132,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= -github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= -github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/spyzhov/ajson v0.9.0 h1:tF46gJGOenYVj+k9K1U1XpCxVWhmiyY5PsVCAs1+OJ0= github.com/spyzhov/ajson v0.9.0/go.mod h1:a6oSw0MMb7Z5aD2tPoPO+jq11ETKgXUr2XktHdT8Wt8= @@ -3200,19 +3173,9 @@ github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSW github.com/substrait-io/substrait-go v0.4.2/go.mod h1:qhpnLmrcvAnlZsUyPXZRqldiHapPTXC3t7xFgDi3aQg= github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA= github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0= -github.com/testcontainers/testcontainers-go v0.30.0 h1:jmn/XS22q4YRrcMwWg0pAwlClzs/abopbsBzrepyc4E= -github.com/testcontainers/testcontainers-go v0.30.0/go.mod h1:K+kHNGiM5zjklKjgTtcrEetF3uhWbMUyqAQoyoh8Pf0= -github.com/testcontainers/testcontainers-go/modules/mysql v0.30.0 h1:wrePvxfU/2HFALKyBqpNs6VoPPvThzHy9aN+PCxse9g= -github.com/testcontainers/testcontainers-go/modules/mysql v0.30.0/go.mod h1:Srnlf7wwA7s6K4sKKhjAoBHJcKorRINR/i5dCA4ZyGk= -github.com/testcontainers/testcontainers-go/modules/postgres v0.30.0 h1:D3HFqpZS90iRGAN7M85DFiuhPfvYvFNnx8urQ6mPAvo= -github.com/testcontainers/testcontainers-go/modules/postgres v0.30.0/go.mod h1:e1sKxwUOkqzvaqdHl/oV9mUtFmkDPTfBGp0po2tnWQU= github.com/thanos-io/objstore v0.0.0-20220809103346-8ef1f215e2bf h1:onQsPyHlq2yIWU+Nfl6yStuqnZuVQQN8FZ8sBb2wqtw= github.com/thanos-io/objstore v0.0.0-20220809103346-8ef1f215e2bf/go.mod h1:v0NhuxxxUFUPatQcVNSCUkBEVezXzl7LSdaBOZygq98= github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= -github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= -github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= -github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= @@ -3282,8 +3245,6 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= -github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= -github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zclconf/go-cty v1.13.0 h1:It5dfKTTZHe9aeppbNOda3mN7Ag7sg6QkBNm6TkyFa0= github.com/zclconf/go-cty v1.13.0/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= @@ -3343,8 +3304,8 @@ go.opentelemetry.io/collector/semconv v0.98.0/go.mod h1:8ElcRZ8Cdw5JnvhTOQOdYizk go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 h1:hCq2hNMwsegUvPzI7sPOvtO9cqyy5GbWt/Ybp2xrx8Q= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0/go.mod h1:LqaApwGx/oUmzsbqxkzuBvyoPpkxk3JQWnqfVrJ3wCA= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.55.0 h1:sqmsIQ75l6lfZjjpnXXT9DFVtYEDg6CH0/Cn4/3A1Wg= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.55.0/go.mod h1:rsg1EO8LXSs2po50PB5CeY/MSVlhghuKBgXlKnqm6ks= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= @@ -3369,11 +3330,11 @@ go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDI go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0/go.mod h1:h95q0LBGh7hlAC08X2DhSeyIG02YQ0UyioTCVAqRPmc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.25.0/go.mod h1:8GlBGcDk8KKi7n+2S4BT/CPZQYH3erLu0/k64r1MYgo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 h1:nSiV3s7wiCam610XcLbYOmMfJxB9gO4uK3Xgv5gmTgg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0/go.mod h1:hKn/e/Nmd19/x1gvIHwtOwVWM+VhuITSWip3JUDghj0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 h1:m0yTiGDLUvVYaTFbAvCkVYIYcvwKt3G7OLoN77NUs/8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0/go.mod h1:wBQbT4UekBfegL2nx0Xk1vBcnzyBPsIVm9hRG4fYcr4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.25.0/go.mod h1:e7ciERRhZaOZXVjx5MiL8TK5+Xv7G5Gv5PA2ZDEJdL8= go.opentelemetry.io/otel/metric v1.17.0/go.mod h1:h4skoxdZI17AxwITdmdZjjYJQH5nzijUUjm+wtPph5o= go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= @@ -3388,8 +3349,8 @@ go.opentelemetry.io/otel/sdk v1.17.0/go.mod h1:U87sE0f5vQB7hwUoW98pW5Rz4ZDuCFBZF go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= +go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY= go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= @@ -3509,8 +3470,8 @@ golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N0 golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -4048,8 +4009,8 @@ gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= gonum.org/v1/gonum v0.12.0/go.mod h1:73TDxJfAAHeA8Mk9mf8NlIppyhQNo5GLTcYeqgo2lvY= -gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= -gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= +gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0= +gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= @@ -4347,8 +4308,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240213162025-012b6fc9bca9/go. google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be/go.mod h1:dvdCTIoAGbkWbcIKBniID56/7XHTt6WfxXNMxuziJ+w= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230807174057-1744710a1577/go.mod h1:NjCQG/D8JandXxM57PZbAJL1DCNL6EypA0vPPwfsc7c= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405/go.mod h1:GRUCuLdzVqZte8+Dl/D4N25yLzcGqqWaYkeVOwulFqw= @@ -4393,8 +4354,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240415141817-7cd4c1c1f9ec/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -4460,8 +4421,8 @@ google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJai google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= -google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= -google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= +google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/grpc/cmd/protoc-gen-go-grpc v0.0.0-20200910201057-6591123024b3/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -4591,6 +4552,8 @@ modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.37.0/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= modernc.org/cc/v3 v3.38.1/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= +modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= +modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= modernc.org/ccgo/v3 v3.0.0-20220904174949-82d86e1b6d56/go.mod h1:YSXjPL62P2AMSxBphRHPn7IkzhVHqkvOnRKAKh+W6ZI= @@ -4601,9 +4564,13 @@ modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aw modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= modernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8/go.mod h1:fUB3Vn0nVPReA+7IG7yZDfjv1TMWjhQP8gCxrFAtL5g= modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= +modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y= +modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s= modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= +modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= @@ -4622,8 +4589,8 @@ modernc.org/libc v1.21.2/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug= modernc.org/libc v1.22.4/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= -modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk= -modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY= +modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= +modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= @@ -4635,15 +4602,18 @@ modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.3.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= -modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= -modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= +modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= modernc.org/sqlite v1.18.2/go.mod h1:kvrTLEWgxUcHa2GfHBQtanR1H9ht3hTJNtKpzH9k1u0= modernc.org/sqlite v1.21.2/go.mod h1:cxbLkB5WS32DnQqeH4h4o1B0eMr8W/y8/RGuxQ3JsC0= -modernc.org/sqlite v1.29.6 h1:0lOXGrycJPptfHDuohfYgNqoe4hu+gYuN/pKgY5XjS4= -modernc.org/sqlite v1.29.6/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U= +modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM= +modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= diff --git a/go.work.sum b/go.work.sum index 8fbaee04958..eb78119b883 100644 --- a/go.work.sum +++ b/go.work.sum @@ -2,6 +2,8 @@ buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-2023080216373 buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20230802163732-1c33ebd9ecfa.1/go.mod h1:xafc+XIsTxTy76GJQ1TKgvJWsSugFBqMaN27WhUblew= cel.dev/expr v0.15.0 h1:O1jzfJCQBfL5BFoYktaxwIhuttaQPsVWerH9/EEKx0w= cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg= +cel.dev/expr v0.16.0 h1:yloc84fytn4zmJX2GU3TkXGsaieaV7dQ057Qs4sIG2Y= +cel.dev/expr v0.16.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg= cloud.google.com/go/accessapproval v1.7.11 h1:MgtE8CI+YJWPGGHnxQ9z1VQqV87h+vSGy2MeM/m0ggQ= cloud.google.com/go/accessapproval v1.7.11/go.mod h1:KGK3+CLDWm4BvjN0wFtZqdFUGhxlTvTF6PhAwQJGL4M= cloud.google.com/go/accesscontextmanager v1.8.11 h1:IQ3KLJmNKPgstN0ZcRw0niU4KfsiOZmzvcGCF+NT618= @@ -259,7 +261,19 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUu docker.io/go-docker v1.0.0 h1:VdXS/aNYQxyA9wdLD5z8Q8Ro688/hG8HzKxYVEVbE6s= docker.io/go-docker v1.0.0/go.mod h1:7tiAn5a0LFmjbPDbyTPOaTTOuG1ZRNXdPA6RvKY+fpY= gioui.org v0.0.0-20210308172011-57750fc8a0a6 h1:K72hopUosKG3ntOPNG4OzzbuhxGuVf06fa2la1/H/Ho= +gioui.org v0.2.0 h1:RbzDn1h/pCVf/q44ImQSa/J3MIFpY3OWphzT/Tyei+w= +gioui.org v0.2.0/go.mod h1:1H72sKEk/fNFV+l0JNeM2Dt3co3Y4uaQcD+I+/GQ0e4= +gioui.org/cpu v0.0.0-20220412190645-f1e9e8c3b1f7 h1:tNJdnP5CgM39PRc+KWmBRRYX/zJ+rd5XaYxY5d5veqA= +gioui.org/cpu v0.0.0-20220412190645-f1e9e8c3b1f7/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= +gioui.org/shader v1.0.6 h1:cvZmU+eODFR2545X+/8XucgZdTtEjR3QWW6W65b0q5Y= +gioui.org/shader v1.0.6/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM= +gioui.org/x v0.2.0 h1:/MbdjKH19F16auv19UiQxli2n6BYPw7eyh9XBOTgmEw= +gioui.org/x v0.2.0/go.mod h1:rCGN2nZ8ZHqrtseJoQxCMZpt2xrZUrdZ2WuMRLBJmYs= +git.sr.ht/~sbinet/cmpimg v0.1.0 h1:E0zPRk2muWuCqSKSVZIWsgtU9pjsw3eKHi8VmQeScxo= +git.sr.ht/~sbinet/cmpimg v0.1.0/go.mod h1:FU12psLbF4TfNXkKH2ZZQ29crIqoiqTZmeQ7dkp/pxE= git.sr.ht/~sbinet/gg v0.3.1 h1:LNhjNn8DerC8f9DHLz6lS0YYul/b602DUxDgGkd/Aik= +git.sr.ht/~sbinet/gg v0.5.0 h1:6V43j30HM623V329xA9Ntq+WJrMjDxRjuAB1LFWF5m8= +git.sr.ht/~sbinet/gg v0.5.0/go.mod h1:G2C0eRESqlKhS7ErsNey6HHrqU1PwsnCQlekFi9Q2Oo= github.com/99designs/basicauth-go v0.0.0-20160802081356-2a93ba0f464d h1:j6oB/WPCigdOkxtuPl1VSIiLpy7Mdsu6phQffbF19Ng= github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e h1:rl2Aq4ZODqTDkeSqQBy+fzpZPamacO1Srp8zq7jf2Sc= github.com/Azure/azure-amqp-common-go/v3 v3.2.3 h1:uDF62mbd9bypXWi19V1bN5NZEO84JqgmI5G73ibAmrk= @@ -281,8 +295,12 @@ github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWV github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= github.com/ClickHouse/ch-go v0.58.2 h1:jSm2szHbT9MCAB1rJ3WuCJqmGLi5UTjlNu+f530UTS0= github.com/ClickHouse/ch-go v0.58.2/go.mod h1:Ap/0bEmiLa14gYjCiRkYGbXvbe8vwdrfTYWhsuQ99aw= +github.com/ClickHouse/ch-go v0.61.5 h1:zwR8QbYI0tsMiEcze/uIMK+Tz1D3XZXLdNrlaOpeEI4= +github.com/ClickHouse/ch-go v0.61.5/go.mod h1:s1LJW/F/LcFs5HJnuogFMta50kKDO0lf9zzfrbl0RQg= github.com/ClickHouse/clickhouse-go/v2 v2.17.1 h1:ZCmAYWpu75IyEi7+Yrs/uaAjiCGY5wfW5kXo64exkX4= github.com/ClickHouse/clickhouse-go/v2 v2.17.1/go.mod h1:rkGTvFDTLqLIm0ma+13xmcCfr/08Gvs7KmFt1tgiWHQ= +github.com/ClickHouse/clickhouse-go/v2 v2.28.3 h1:SkFzPULX6nzgfNZd1YD1XTECivjTMrCtD09ZPKcVLFQ= +github.com/ClickHouse/clickhouse-go/v2 v2.28.3/go.mod h1:vzn73hp+3JwxtFU4RjPCQ7r6fP2pMKVwdi8E1/Tkua8= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME= @@ -300,6 +318,8 @@ github.com/KimMachineGun/automemlimit v0.6.0 h1:p/BXkH+K40Hax+PuWWPQ478hPjsp9h1C github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o= github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw= +github.com/MicahParks/keyfunc/v2 v2.1.0 h1:6ZXKb9Rp6qp1bDbJefnG7cTH8yMN1IC/4nf+GVjO99k= +github.com/MicahParks/keyfunc/v2 v2.1.0/go.mod h1:rW42fi+xgLJ2FRRXAfNx9ZA8WpD4OeE/yHVMteCkw9k= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/OneOfOne/xxhash v1.2.6 h1:U68crOE3y3MPttCMQGywZOLrTeF5HHJ3/vDBCJn9/bA= github.com/OneOfOne/xxhash v1.2.6/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= @@ -335,10 +355,10 @@ github.com/alexflint/go-scalar v1.0.0 h1:NGupf1XV/Xb04wXskDFzS0KWOLH632W/EO4fAFi github.com/alexflint/go-scalar v1.0.0/go.mod h1:GpHzbCOZXEKMEcygYQ5n/aa4Aq84zbxjy3MxYW0gjYw= github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI= github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= -github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= -github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= +github.com/andybalholm/stroke v0.0.0-20221221101821-bd29b49d73f0 h1:uF5Q/hWnDU1XZeT6CsrRSxHLroUSEYYO3kgES+yd+So= +github.com/andybalholm/stroke v0.0.0-20221221101821-bd29b49d73f0/go.mod h1:ccdDYaY5+gO+cbnQdFxEXqfy0RkoV25H3jLXUDNM3wg= github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 h1:goHVqTbFX3AIo0tzGr14pgfAW2ZfPChKO21Z9MGf/gk= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= @@ -395,6 +415,8 @@ github.com/bufbuild/protovalidate-go v0.2.1/go.mod h1:e7XXDtlxj5vlEyAgsrxpzayp4c github.com/bwesterb/go-ristretto v1.2.3 h1:1w53tCkGhCQ5djbat3+MH0BAQ5Kfgbt56UZQ/JMzngw= github.com/bytedance/sonic v1.10.0-rc3 h1:uNSnscRapXTwUgTyOF0GVljYD08p9X/Lbr9MweSV3V0= github.com/bytedance/sonic v1.10.0-rc3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= +github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= github.com/casbin/casbin/v2 v2.37.0 h1:/poEwPSovi4bTOcP752/CsTQiRz2xycyVKFG7GUhbDw= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= @@ -426,7 +448,11 @@ github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9D github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= +github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= +github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= +github.com/containerd/containerd v1.6.8 h1:h4dOFDwzHmqFEP754PgfgTeVXFnLiRc6kiqC7tplDJs= +github.com/containerd/containerd v1.6.8/go.mod h1:By6p5KqPK0/7/CgO/A6t/Gz+CUYUu2zf1hUaaymVXB0= github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04= github.com/coreos/go-etcd v2.0.0+incompatible h1:bXhRBIXoTm9BYHS3gE0TtQuyNZyeEMux2sDi4oo5YOo= github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk= @@ -492,6 +518,7 @@ github.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQ github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/expr-lang/expr v1.16.2 h1:JvMnzUs3LeVHBvGFcXYmXo+Q6DPDmzrlcSBO6Wy3w4s= github.com/expr-lang/expr v1.16.2/go.mod h1:uCkhfG+x7fcZ5A5sXHKuQ07jGZRl6J0FCAaf2k4PtVQ= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= @@ -518,20 +545,36 @@ github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= github.com/go-faster/errors v0.6.1 h1:nNIPOBkprlKzkThvS/0YaX8Zs9KewLCOSFQS5BU06FI= github.com/go-faster/errors v0.6.1/go.mod h1:5MGV2/2T9yvlrbhe9pD9LO5Z/2zCSq2T8j+Jpi2LAyY= +github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= +github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= github.com/go-fonts/dejavu v0.1.0 h1:JSajPXURYqpr+Cu8U9bt8K+XcACIHWqWrvWCKyeFmVQ= +github.com/go-fonts/dejavu v0.3.2 h1:3XlHi0JBYX+Cp8n98c6qSoHrxPa4AUKDMKdrh/0sUdk= +github.com/go-fonts/dejavu v0.3.2/go.mod h1:m+TzKY7ZEl09/a17t1593E4VYW8L1VaBXHzFZOIjGEY= github.com/go-fonts/latin-modern v0.2.0 h1:5/Tv1Ek/QCr20C6ZOz15vw3g7GELYL98KWr8Hgo+3vk= +github.com/go-fonts/latin-modern v0.3.2 h1:M+Sq24Dp0ZRPf3TctPnG1MZxRblqyWC/cRUL9WmdaFc= +github.com/go-fonts/latin-modern v0.3.2/go.mod h1:9odJt4NbRrbdj4UAMuLVd4zEukf6aAEKnDaQga0whqQ= github.com/go-fonts/liberation v0.3.0 h1:3BI2iaE7R/s6uUUtzNCjo3QijJu3aS4wmrMgfSpYQ+8= github.com/go-fonts/liberation v0.3.0/go.mod h1:jdJ+cqF+F4SUL2V+qxBth8fvBpBDS7yloUL5Fi8GTGY= +github.com/go-fonts/liberation v0.3.2 h1:XuwG0vGHFBPRRI8Qwbi5tIvR3cku9LUfZGq/Ar16wlQ= +github.com/go-fonts/liberation v0.3.2/go.mod h1:N0QsDLVUQPy3UYg9XAc3Uh3UDMp2Z7M1o4+X98dXkmI= github.com/go-fonts/stix v0.1.0 h1:UlZlgrvvmT/58o573ot7NFw0vZasZ5I6bcIft/oMdgg= +github.com/go-fonts/stix v0.2.2 h1:v9krocr13J1llaOHLEol1eaHsv8S43UuFX/1bFgEJJ4= +github.com/go-fonts/stix v0.2.2/go.mod h1:SUxggC9dxd/Q+rb5PkJuvfvTbOPtNc2Qaua00fIp9iU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 h1:NxXI5pTAtpEaU49bpLpQoDsu1zrteW/vxzTz8Cd2UAs= github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9/go.mod h1:gWuR/CrFDDeVRFQwHPvsv9soJVB/iqymhuZQuJ3a9OM= +github.com/go-latex/latex v0.0.0-20231108140139-5c1ce85aa4ea h1:DfZQkvEbdmOe+JK2TMtBM+0I9GSdzE2y/L1/AmD8xKc= +github.com/go-latex/latex v0.0.0-20231108140139-5c1ce85aa4ea/go.mod h1:Y7Vld91/HRbTBm7JwoI7HejdDB0u+e9AUBO9MB7yuZk= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI= github.com/go-pdf/fpdf v0.6.0 h1:MlgtGIfsdMEEQJr2le6b/HNr1ZlQwxyWr77r2aj2U/8= +github.com/go-pdf/fpdf v0.9.0 h1:PPvSaUuo1iMi9KkaAn90NuKi+P4gwMedWPHhj8YlJQw= +github.com/go-pdf/fpdf v0.9.0/go.mod h1:oO8N111TkmKb9D7VvWGLvLJlaZUQVPM+6V42pp3iV4Y= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= @@ -539,6 +582,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 h1:FQivqchis6bE2/9uF70M2gmmLpe82esEm2QadL0TEJo= +github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k= github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c= github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= @@ -554,6 +599,8 @@ github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfE github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4= github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/glog v1.2.2 h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY= +github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 h1:EcQR3gusLHN46TAD+G+EbaaqJArt5vHhNpXAa12PQf4= github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= @@ -585,8 +632,6 @@ github.com/grafana/alerting v0.0.0-20240830172655-aa466962ea18/go.mod h1:GMLi6d0 github.com/grafana/alerting v0.0.0-20240917171353-6c25eb6eff10 h1:oDbLKM34O+JUF9EQFS+9aYhdYoeNfUpXqNjFCLIxwF4= github.com/grafana/alerting v0.0.0-20240917171353-6c25eb6eff10/go.mod h1:GMLi6d09Xqo96fCVUjNk//rcjP5NKEdjOzfWIffD5r4= github.com/grafana/gomemcache v0.0.0-20240229205252-cd6a66d6fb56/go.mod h1:PGk3RjYHpxMM8HFPhKKo+vve3DdlPUELZLSDEFehPuU= -github.com/grafana/prometheus-alertmanager v0.25.1-0.20240625192351-66ec17e3aa45 h1:AJKOtDKAOg8XNFnIZSmqqqutoTSxVlRs6vekL2p2KEY= -github.com/grafana/prometheus-alertmanager v0.25.1-0.20240625192351-66ec17e3aa45/go.mod h1:01sXtHoRwI8W324IPAzuxDFOmALqYLCOhvSC2fUHWXc= github.com/grafana/pyroscope-go/godeltaprof v0.1.6/go.mod h1:Tk376Nbldo4Cha9RgiU7ik8WKFkNpfds98aUzS8omLE= github.com/grafana/thema v0.0.0-20230511182720-3146087fcc26 h1:HX927q4X1n451pnGb8U0wq74i8PCzuxVjzv7TyD10kc= github.com/grafana/thema v0.0.0-20230511182720-3146087fcc26/go.mod h1:Pn9nfzCk7nV0mvNgwusgCjCROZP6nm4GpwTnmEhLT24= @@ -689,8 +734,12 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1 h1:vi1F1IQ8N7hNWytK9DpJsUfQhGuNSc19z330K6vl4zk= github.com/logrusorgru/aurora/v3 v3.0.0 h1:R6zcoZZbvVcGMvDCKo45A9U/lzYyzl5NfYIvznmDfE4= github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc= +github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c h1:VtwQ41oftZwlMnOEbMWQtSEUgU64U4s+GHk7hZK+jtY= +github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= github.com/lyft/protoc-gen-star v0.6.1 h1:erE0rdztuaDq3bpGifD95wfoPrSZc95nGA6tbiNYh6M= github.com/lyft/protoc-gen-star/v2 v2.0.3 h1:/3+/2sWyXeMLzKd1bX+ixWKgEMsULrIivpDsuaF441o= +github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4 h1:sIXJOMrYnQZJu7OB7ANSF4MYri2fTEGIsRLz6LwI4xE= +github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= github.com/lyft/protoc-gen-validate v0.0.13 h1:KNt/RhmQTOLr7Aj8PsJ7mTronaFyx80mRTT9qF261dA= github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw= github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= @@ -700,6 +749,8 @@ github.com/matryer/moq v0.5.0 h1:h2PJUYjZSiyEahzVogDRmrgL9Bsx9xYAl8l+LPfmwL8= github.com/matryer/moq v0.5.0/go.mod h1:39GTnrD0mVWHPvWdYj5ki/lxfhLQEtHcLh+tWoYF/iE= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= +github.com/mfridman/xflag v0.0.0-20240825232106-efb77353e578 h1:CRrqlUmLebb/QjzRDWE0E66+YyN/v95+w6WyH9ju8/Y= +github.com/mfridman/xflag v0.0.0-20240825232106-efb77353e578/go.mod h1:/483ywM5ZO5SuMVjrIGquYNE5CzLrj5Ux/LxWWnjRaE= github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= @@ -722,8 +773,12 @@ github.com/nats-io/jwt/v2 v2.0.3 h1:i/O6cmIsjpcQyWDYNcq2JyZ3/VTF8SJ4JWluI5OhpvI= github.com/nats-io/nats-server/v2 v2.5.0 h1:wsnVaaXH9VRSg+A2MVg5Q727/CqxnmPLGFQ3YZYKTQg= github.com/nats-io/nats.go v1.31.0 h1:/WFBHEc/dOKBF6qf1TZhrdEfTmOZ5JzdJ+Y3m6Y/p7E= github.com/nats-io/nats.go v1.31.0/go.mod h1:di3Bm5MLsoB4Bx61CBTsxuarI36WbhAwOm8QrW39+i8= +github.com/nats-io/nats.go v1.34.0 h1:fnxnPCNiwIG5w08rlMcEKTUw4AV/nKyGCOJE8TdhSPk= +github.com/nats-io/nats.go v1.34.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= github.com/nats-io/nkeys v0.4.6 h1:IzVe95ru2CT6ta874rt9saQRkWfe2nFj1NtvYSLqMzY= github.com/nats-io/nkeys v0.4.6/go.mod h1:4DxZNzenSVd1cYQoAa8948QY3QDjrHfcfVADymtkpts= +github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= +github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1 h1:dOYG7LS/WK00RWZc8XGgcUTlTxpp3mKhdR2Q9z9HbXM= @@ -782,6 +837,8 @@ github.com/parquet-go/parquet-go v0.20.2-0.20240416173845-962b3c5827c3 h1:dHzXGq github.com/parquet-go/parquet-go v0.20.2-0.20240416173845-962b3c5827c3/go.mod h1:wMYanjuaE900FTDTNY00JU+67Oqh9uO0pYWRNoPGctQ= github.com/paulmach/orb v0.10.0 h1:guVYVqzxHE/CQ1KpfGO077TR0ATHSNjp4s6XGLn3W9s= github.com/paulmach/orb v0.10.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= +github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU= +github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30 h1:BHT1/DKsYDGkUgQ2jmMaozVcdk+sVfz0+1ZJq4zkWgw= github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= @@ -800,6 +857,8 @@ github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDj github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= +github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c h1:NRoLoZvkBTKvR5gQLgA3e0hqjkY9u1wm+iOL45VN/qI= +github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc= github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= @@ -823,6 +882,8 @@ github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245 h1:K1Xf3bKttbF github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk= github.com/sagikazarmark/crypt v0.17.0 h1:ZA/7pXyjkHoK4bW4mIdnCLvL8hd+Nrbiw7Dqk7D4qUk= github.com/sagikazarmark/crypt v0.17.0/go.mod h1:SMtHTvdmsZMuY/bpZoqokSoChIrcJ/epOxZN58PbZDg= +github.com/sagikazarmark/crypt v0.19.0 h1:WMyLTjHBo64UvNcWqpzY3pbZTYgnemZU8FBZigKc42E= +github.com/sagikazarmark/crypt v0.19.0/go.mod h1:c6vimRziqqERhtSe0MhIvzE1w54FrCHtrXb5NH/ja78= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da h1:p3Vo3i64TCLY7gIfzeQaUJ+kppEO5WQG3cL8iE8tGHU= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= @@ -834,6 +895,10 @@ github.com/segmentio/fasthash v0.0.0-20180216231524-a72b379d632e/go.mod h1:tm/wZ github.com/sercand/kuberesolver/v5 v5.1.1 h1:CYH+d67G0sGBj7q5wLK61yzqJJ8gLLC8aeprPTHb6yY= github.com/sercand/kuberesolver/v5 v5.1.1/go.mod h1:Fs1KbKhVRnB2aDWN12NjKCB+RgYMWZJ294T3BtmVCpQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shirou/gopsutil/v3 v3.24.2 h1:kcR0erMbLg5/3LcInpw0X/rrPSqq4CDPyI6A6ZRC18Y= +github.com/shirou/gopsutil/v3 v3.24.2/go.mod h1:tSg/594BcA+8UdQU2XcW803GWYgdtauFFPgJCJKZlVk= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v1.7.1 h1:UJcjSAI3aUKx52kfcfhblgyhZceouhvvs3OYdWgn+PY= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/sony/gobreaker v0.4.1 h1:oMnRNZXX5j85zso6xCPRNPtmAycat+WcoKbklScLDgQ= @@ -854,14 +919,22 @@ github.com/tdewolff/parse/v2 v2.6.8 h1:mhNZXYCx//xG7Yq2e/kVLNZw4YfYmeHbhx+Zc0OvF github.com/tdewolff/parse/v2 v2.6.8/go.mod h1:XHDhaU6IBgsryfdnpzUXBlT6leW/l25yrFBTEb4eIyM= github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94= +github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tursodatabase/libsql-client-go v0.0.0-20240411070317-a1138d155304 h1:Y6cw8yjWCEJDy5Bll7HjTinkgTQU55AXiKSEe29SpgA= github.com/tursodatabase/libsql-client-go v0.0.0-20240411070317-a1138d155304/go.mod h1:2Fu26tjM011BLeR5+jwTfs6DX/fNMEWV/3CBZvggrA4= +github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d h1:dOMI4+zEbDI37KGb0TI44GUAwxHF9cMsIoDTJ7UmgfU= +github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d/go.mod h1:l8xTsYB90uaVdMHXMCxKKLSgw5wLYBwBKKefNIUnm9s= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= @@ -903,12 +976,18 @@ github.com/yalue/merged_fs v1.3.0 h1:qCeh9tMPNy/i8cwDsQTJ5bLr6IRxbs6meakNE5O+wyY github.com/yalue/merged_fs v1.3.0/go.mod h1:WqqchfVYQyclV2tnR7wtRhBddzBvLVR83Cjw9BKQw0M= github.com/ydb-platform/ydb-go-genproto v0.0.0-20240126124512-dbb0e1720dbf h1:ckwNHVo4bv2tqNkgx3W3HANh3ta1j6TR5qw08J1A7Tw= github.com/ydb-platform/ydb-go-genproto v0.0.0-20240126124512-dbb0e1720dbf/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I= +github.com/ydb-platform/ydb-go-genproto v0.0.0-20240528144234-5d5a685e41f7 h1:nL8XwD6fSst7xFUirkaWJmE7kM0CdWRYgu6+YQer1d4= +github.com/ydb-platform/ydb-go-genproto v0.0.0-20240528144234-5d5a685e41f7/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I= github.com/ydb-platform/ydb-go-sdk/v3 v3.55.1 h1:Ebo6J5AMXgJ3A438ECYotA0aK7ETqjQx9WoZvVxzKBE= github.com/ydb-platform/ydb-go-sdk/v3 v3.55.1/go.mod h1:udNPW8eupyH/EZocecFmaSNJacKKYjzQa7cVgX5U2nc= +github.com/ydb-platform/ydb-go-sdk/v3 v3.80.2 h1:qmZGJQCNx09/r0HDIT2cDDogiOvWikELy13ubM2CFS8= +github.com/ydb-platform/ydb-go-sdk/v3 v3.80.2/go.mod h1:IHwuXyolaAmGK2Dp7+dlhsnXphG1pwCoaP/OITT3+tU= github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA= github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0= github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= @@ -1022,9 +1101,15 @@ golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5D golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20230321023759-10a507213a29/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-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp/shiny v0.0.0-20230801115018-d63ba01acd4b h1:sgkbz1SFTsoQIvzTIw45hccUcGocu00QM3qucBYV8b0= +golang.org/x/exp/shiny v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0= golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4= golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0= +golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= +golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= @@ -1049,17 +1134,20 @@ golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc= gonum.org/v1/plot v0.10.1 h1:dnifSs43YJuNMDzB7v8wV64O4ABBHReuAVAoBxqBqS4= +gonum.org/v1/plot v0.14.0 h1:+LBDVFYwFe4LHhdP8coW6296MBEY4nQ+Y4vuUpJopcE= +gonum.org/v1/plot v0.14.0/go.mod h1:MLdR9424SJed+5VqC6MsouEpig9pZX2VZ57H9ko2bXU= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:mCr1K1c8kX+1iSBREvU3Juo11CB+QOEWxbRS01wWl5M= google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= @@ -1075,8 +1163,10 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/cheggaaa/pb.v1 v1.0.25 h1:Ev7yu1/f6+d+b3pi5vPdRPc6nNtP1umSfcWiEfRqv6I= @@ -1107,13 +1197,14 @@ k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 h1:pWEwq4Asjm4vjW7vcsmijwBhOr1/s k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 h1:NGrVE502P0s0/1hudf8zjgwki1X/TByhmAoILTarmzo= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= +modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q= modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y= +modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0= modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI= modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= -modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws= modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE= nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= diff --git a/package.json b/package.json index e261fc3abcf..6bf41f692ec 100644 --- a/package.json +++ b/package.json @@ -69,8 +69,8 @@ "releaseNotesUrl": "https://grafana.com/docs/grafana/next/release-notes/" }, "devDependencies": { - "@babel/core": "7.25.7", - "@babel/preset-env": "7.25.7", + "@babel/core": "7.25.8", + "@babel/preset-env": "7.25.8", "@babel/runtime": "7.25.7", "@betterer/betterer": "5.4.0", "@betterer/cli": "5.4.0", @@ -84,10 +84,10 @@ "@manypkg/get-packages": "^2.2.0", "@playwright/test": "1.48.0", "@pmmmwh/react-refresh-webpack-plugin": "0.5.15", - "@react-types/button": "3.9.6", + "@react-types/button": "3.10.0", "@react-types/menu": "3.9.12", "@react-types/overlays": "3.8.10", - "@react-types/shared": "3.24.1", + "@react-types/shared": "3.25.0", "@rtk-query/codegen-openapi": "^1.2.0", "@rtsao/plugin-proposal-class-properties": "7.0.1-patch.1", "@swc/core": "1.4.2", @@ -171,7 +171,7 @@ "cypress-recurse": "^1.35.3", "esbuild": "0.24.0", "esbuild-loader": "4.2.2", - "esbuild-plugin-browserslist": "^0.14.0", + "esbuild-plugin-browserslist": "^0.15.0", "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-import": "^2.26.0", @@ -222,7 +222,7 @@ "redux-mock-store": "1.5.4", "rimraf": "6.0.1", "rudder-sdk-js": "2.48.19", - "sass": "1.79.3", + "sass": "1.79.4", "sass-loader": "16.0.2", "smtp-tester": "^2.1.0", "style-loader": "4.0.0", @@ -250,13 +250,13 @@ "@emotion/react": "11.13.3", "@fingerprintjs/fingerprintjs": "^3.4.2", "@floating-ui/react": "0.26.24", - "@formatjs/intl-durationformat": "^0.2.4", + "@formatjs/intl-durationformat": "^0.3.0", "@glideapps/glide-data-grid": "^6.0.0", "@grafana/aws-sdk": "0.5.0", "@grafana/azure-sdk": "0.0.3", "@grafana/data": "workspace:*", "@grafana/e2e-selectors": "workspace:*", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/faro-core": "^1.3.6", "@grafana/faro-web-sdk": "^1.3.6", "@grafana/faro-web-tracing": "^1.8.2", @@ -268,11 +268,12 @@ "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", "@grafana/saga-icons": "workspace:*", - "@grafana/scenes": "5.18.3", + "@grafana/scenes": "5.20.0", + "@grafana/scenes-react": "5.20.0", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", - "@hello-pangea/dnd": "16.6.0", + "@hello-pangea/dnd": "17.0.0", "@kusto/monaco-kusto": "^10.0.0", "@leeoniya/ufuzzy": "1.0.14", "@lezer/common": "1.2.2", diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index b8124c22120..6283847aa1a 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -198,7 +198,6 @@ export interface FeatureToggles { zanzana?: boolean; passScopeToDashboardApi?: boolean; alertingApiServer?: boolean; - dashboardRestoreUI?: boolean; cloudWatchRoundUpEndTime?: boolean; cloudwatchMetricInsightsCrossAccount?: boolean; prometheusAzureOverrideAudience?: boolean; diff --git a/packages/grafana-data/src/types/icon.ts b/packages/grafana-data/src/types/icon.ts index 570437c943d..d88e76965a6 100644 --- a/packages/grafana-data/src/types/icon.ts +++ b/packages/grafana-data/src/types/icon.ts @@ -22,6 +22,7 @@ export const availableIconsIndex = { 'angle-up': true, 'align-left': true, 'align-right': true, + 'api-endpoint': true, 'application-observability': true, apps: true, 'archive-alt': true, @@ -49,6 +50,7 @@ export const availableIconsIndex = { bookmark: true, 'book-open': true, 'brackets-curly': true, + 'browser-alt': true, bug: true, building: true, 'calculator-alt': true, @@ -161,6 +163,7 @@ export const availableIconsIndex = { info: true, 'info-circle': true, k6: true, + 'k6-rounded': true, 'key-skeleton-alt': true, keyboard: true, kubernetes: true, @@ -184,6 +187,7 @@ export const availableIconsIndex = { 'minus-circle': true, 'mobile-android': true, monitor: true, + 'multi-step': true, palette: true, 'panel-add': true, paragraph: true, diff --git a/packages/grafana-flamegraph/package.json b/packages/grafana-flamegraph/package.json index 3ccd87c0a02..4767950153e 100644 --- a/packages/grafana-flamegraph/package.json +++ b/packages/grafana-flamegraph/package.json @@ -56,8 +56,8 @@ "tslib": "2.7.0" }, "devDependencies": { - "@babel/core": "7.25.7", - "@babel/preset-env": "7.25.7", + "@babel/core": "7.25.8", + "@babel/preset-env": "7.25.8", "@babel/preset-react": "7.25.7", "@grafana/tsconfig": "^2.0.0", "@rollup/plugin-node-resolve": "15.3.0", diff --git a/packages/grafana-icons/package.json b/packages/grafana-icons/package.json index 7313c7fe3ed..fd2741ca1b1 100644 --- a/packages/grafana-icons/package.json +++ b/packages/grafana-icons/package.json @@ -34,7 +34,7 @@ "build": "yarn generate && rollup -c rollup.config.ts --configPlugin esbuild" }, "devDependencies": { - "@babel/core": "7.25.7", + "@babel/core": "7.25.8", "@grafana/tsconfig": "^2.0.0", "@rollup/plugin-node-resolve": "^15.3.0", "@rollup/plugin-typescript": "^12.1.0", diff --git a/packages/grafana-o11y-ds-frontend/package.json b/packages/grafana-o11y-ds-frontend/package.json index a1e256f8dd1..90f13ecb692 100644 --- a/packages/grafana-o11y-ds-frontend/package.json +++ b/packages/grafana-o11y-ds-frontend/package.json @@ -20,7 +20,7 @@ "@emotion/css": "11.13.4", "@grafana/data": "11.3.0-pre", "@grafana/e2e-selectors": "11.3.0-pre", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/runtime": "11.3.0-pre", "@grafana/schema": "11.3.0-pre", "@grafana/ui": "11.3.0-pre", diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index 5c8924babaf..f0492ea2fe1 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -39,12 +39,12 @@ "@emotion/css": "11.13.4", "@floating-ui/react": "0.26.24", "@grafana/data": "11.3.0-pre", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/faro-web-sdk": "1.10.2", "@grafana/runtime": "11.3.0-pre", "@grafana/schema": "11.3.0-pre", "@grafana/ui": "11.3.0-pre", - "@hello-pangea/dnd": "16.6.0", + "@hello-pangea/dnd": "17.0.0", "@leeoniya/ufuzzy": "1.0.14", "@lezer/common": "1.2.2", "@lezer/highlight": "1.2.1", @@ -131,7 +131,7 @@ "rollup-plugin-dts": "^6.1.1", "rollup-plugin-esbuild": "6.1.1", "rollup-plugin-node-externals": "^7.1.3", - "sass": "1.79.3", + "sass": "1.79.4", "sass-loader": "16.0.2", "style-loader": "4.0.0", "testing-library-selector": "0.3.1", diff --git a/packages/grafana-prometheus/src/configuration/PromSettings.test.tsx b/packages/grafana-prometheus/src/configuration/PromSettings.test.tsx index 15af11210cc..45ac59a46d7 100644 --- a/packages/grafana-prometheus/src/configuration/PromSettings.test.tsx +++ b/packages/grafana-prometheus/src/configuration/PromSettings.test.tsx @@ -112,5 +112,12 @@ describe('PromSettings', () => { fireEvent.blur(input); expect(queryByText(countError)).toBeInTheDocument(); }); + + it('should have a series endpoint configuration element', () => { + const options = defaultProps; + + render( {}} options={options} />); + expect(screen.getByText('Use series endpoint')).toBeInTheDocument(); + }); }); }); diff --git a/packages/grafana-prometheus/src/configuration/PromSettings.tsx b/packages/grafana-prometheus/src/configuration/PromSettings.tsx index c08273b4ff7..9291ec95040 100644 --- a/packages/grafana-prometheus/src/configuration/PromSettings.tsx +++ b/packages/grafana-prometheus/src/configuration/PromSettings.tsx @@ -492,6 +492,26 @@ export const PromSettings = (props: Props) => { + + Checking this option will favor the series endpoint with match[] parameter over the label values + endpoint with match[] parameter. While the label values endpoint is considered more performant, some + users may prefer the series because it has a POST method while the label values endpoint only has a GET + method. {docsTip()} + + } + interactive={true} + disabled={options.readOnly} + className={styles.switchField} + > + + diff --git a/packages/grafana-prometheus/src/datasource.ts b/packages/grafana-prometheus/src/datasource.ts index 29b53f51902..00edfe60784 100644 --- a/packages/grafana-prometheus/src/datasource.ts +++ b/packages/grafana-prometheus/src/datasource.ts @@ -111,6 +111,7 @@ export class PrometheusDatasource cacheLevel: PrometheusCacheLevel; cache: QueryCache; metricNamesAutocompleteSuggestionLimit: number; + seriesEndpoint: boolean; constructor( instanceSettings: DataSourceInstanceSettings, @@ -136,6 +137,7 @@ export class PrometheusDatasource this.customQueryParameters = new URLSearchParams(instanceSettings.jsonData.customQueryParameters); this.datasourceConfigurationPrometheusFlavor = instanceSettings.jsonData.prometheusType; this.datasourceConfigurationPrometheusVersion = instanceSettings.jsonData.prometheusVersion; + this.seriesEndpoint = instanceSettings.jsonData.seriesEndpoint ?? false; this.defaultEditor = instanceSettings.jsonData.defaultEditor; this.disableRecordingRules = instanceSettings.jsonData.disableRecordingRules ?? false; this.variables = new PrometheusVariableSupport(this, this.templateSrv); @@ -183,6 +185,12 @@ export class PrometheusDatasource } hasLabelsMatchAPISupport(): boolean { + // users may choose the series endpoint as it has a POST method + // while the label values is only GET + if (this.seriesEndpoint) { + return false; + } + return ( // https://github.com/prometheus/prometheus/releases/tag/v2.24.0 this._isDatasourceVersionGreaterOrEqualTo('2.24.0', PromApplication.Prometheus) || diff --git a/packages/grafana-prometheus/src/types.ts b/packages/grafana-prometheus/src/types.ts index a8b20b64e87..57903231250 100644 --- a/packages/grafana-prometheus/src/types.ts +++ b/packages/grafana-prometheus/src/types.ts @@ -53,6 +53,7 @@ export interface PromOptions extends DataSourceJsonData { sigV4Auth?: boolean; oauthPassThru?: boolean; codeModeMetricNamesSuggestionLimit?: number; + seriesEndpoint?: boolean; } export type ExemplarTraceIdDestination = { diff --git a/packages/grafana-sql/package.json b/packages/grafana-sql/package.json index b291478a8f3..806a2c99540 100644 --- a/packages/grafana-sql/package.json +++ b/packages/grafana-sql/package.json @@ -17,7 +17,7 @@ "@emotion/css": "11.13.4", "@grafana/data": "11.3.0-pre", "@grafana/e2e-selectors": "11.3.0-pre", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/runtime": "11.3.0-pre", "@grafana/ui": "11.3.0-pre", "@react-awesome-query-builder/ui": "6.6.3", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 233095b9f89..1ce22d07abd 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -55,7 +55,7 @@ "@grafana/e2e-selectors": "11.3.0-pre", "@grafana/faro-web-sdk": "^1.3.6", "@grafana/schema": "11.3.0-pre", - "@hello-pangea/dnd": "16.6.0", + "@hello-pangea/dnd": "17.0.0", "@leeoniya/ufuzzy": "1.0.14", "@monaco-editor/react": "4.6.0", "@popperjs/core": "2.11.8", @@ -115,7 +115,7 @@ "uuid": "9.0.1" }, "devDependencies": { - "@babel/core": "7.25.7", + "@babel/core": "7.25.8", "@faker-js/faker": "^9.0.0", "@grafana/tsconfig": "^2.0.0", "@rollup/plugin-node-resolve": "15.3.0", diff --git a/packages/grafana-ui/src/components/Input/AutoSizeInput.test.tsx b/packages/grafana-ui/src/components/Input/AutoSizeInput.test.tsx index b47fc7da3d9..b2756e7b2d9 100644 --- a/packages/grafana-ui/src/components/Input/AutoSizeInput.test.tsx +++ b/packages/grafana-ui/src/components/Input/AutoSizeInput.test.tsx @@ -15,6 +15,16 @@ jest.mock('../../utils/measureText', () => { }); describe('AutoSizeInput', () => { + it('should support default Input API', () => { + const onChange = jest.fn(); + render(); + + const input: HTMLInputElement = screen.getByTestId('autosize-input'); + fireEvent.change(input, { target: { value: 'foo' } }); + + expect(onChange).toHaveBeenCalled(); + }); + it('should have default minWidth when empty', () => { render(); diff --git a/packages/grafana-ui/src/components/Input/AutoSizeInput.tsx b/packages/grafana-ui/src/components/Input/AutoSizeInput.tsx index de855ff5302..d1e61001de3 100644 --- a/packages/grafana-ui/src/components/Input/AutoSizeInput.tsx +++ b/packages/grafana-ui/src/components/Input/AutoSizeInput.tsx @@ -20,6 +20,7 @@ export const AutoSizeInput = React.forwardRef((props, r minWidth = 10, maxWidth, onCommitChange, + onChange, onKeyDown, onBlur, value: controlledValue, @@ -48,6 +49,9 @@ export const AutoSizeInput = React.forwardRef((props, r ref={ref} value={value.toString()} onChange={(event) => { + if (onChange) { + onChange(event); + } setValue(event.currentTarget.value); }} width={inputWidth} diff --git a/packages/grafana-ui/src/components/Table/utils.test.ts b/packages/grafana-ui/src/components/Table/utils.test.ts index 6f1d7f937f5..4883ffec578 100644 --- a/packages/grafana-ui/src/components/Table/utils.test.ts +++ b/packages/grafana-ui/src/components/Table/utils.test.ts @@ -545,7 +545,8 @@ describe('Table utils', () => { }); describe('guessLongestField', () => { - it('should guess the longest field correct if there are few records', () => { + // FLAKY TEST - https://drone.grafana.net/grafana/grafana/201232/1/5 + it.skip('should guess the longest field correct if there are few records', () => { const data = getWrappableData(10); const config = { defaults: { diff --git a/pkg/aggregator/go.mod b/pkg/aggregator/go.mod index fd0a44d51e4..601011db495 100644 --- a/pkg/aggregator/go.mod +++ b/pkg/aggregator/go.mod @@ -23,7 +23,7 @@ require ( require ( github.com/BurntSushi/toml v1.4.0 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect - github.com/antlr4-go/antlr/v4 v4.13.0 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/apache/arrow/go/v15 v15.0.2 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -53,7 +53,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.2 // indirect - github.com/google/cel-go v0.20.1 // indirect + github.com/google/cel-go v0.21.0 // indirect github.com/google/flatbuffers v24.3.25+incompatible // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect @@ -98,7 +98,7 @@ require ( github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.20.3 // indirect + github.com/prometheus/client_golang v1.20.4 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect @@ -118,21 +118,21 @@ require ( go.etcd.io/etcd/api/v3 v3.5.14 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.14 // indirect go.etcd.io/etcd/client/v3 v3.5.14 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.55.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect go.opentelemetry.io/contrib/propagators/jaeger v1.30.0 // indirect go.opentelemetry.io/contrib/samplers/jaegerremote v0.23.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 // indirect go.opentelemetry.io/otel/metric v1.30.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect + go.opentelemetry.io/otel/sdk v1.30.0 // indirect go.opentelemetry.io/otel/trace v1.30.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.27.0 // indirect - golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect + golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect @@ -143,11 +143,11 @@ require ( golang.org/x/time v0.6.0 // indirect golang.org/x/tools v0.24.0 // indirect golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect - gonum.org/v1/gonum v0.14.0 // indirect + gonum.org/v1/gonum v0.15.1 // indirect google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.66.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/grpc v1.67.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect diff --git a/pkg/aggregator/go.sum b/pkg/aggregator/go.sum index c52f20d6294..51002ade3a7 100644 --- a/pkg/aggregator/go.sum +++ b/pkg/aggregator/go.sum @@ -5,8 +5,8 @@ github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0 github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= -github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/apache/arrow/go/v15 v15.0.2 h1:60IliRbiyTWCWjERBCkO1W4Qun9svcYoZrSLcyOsMLE= github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+yea1jass9YXgjA= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= @@ -106,8 +106,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= -github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= +github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= +github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI= github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= @@ -247,8 +247,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN 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/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= -github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4= -github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= +github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= 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= @@ -342,8 +342,8 @@ go.etcd.io/etcd/raft/v3 v3.5.13 h1:7r/NKAOups1YnKcfro2RvGGo2PTuizF/xh26Z2CTAzA= go.etcd.io/etcd/raft/v3 v3.5.13/go.mod h1:uUFibGLn2Ksm2URMxN1fICGhk8Wu96EfDQyuLhAcAmw= go.etcd.io/etcd/server/v3 v3.5.13 h1:V6KG+yMfMSqWt+lGnhFpP5z5dRUj1BDRJ5k1fQ9DFok= go.etcd.io/etcd/server/v3 v3.5.13/go.mod h1:K/8nbsGupHqmr5MkgaZpLlH1QdX1pcNQLAkODy44XcQ= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 h1:hCq2hNMwsegUvPzI7sPOvtO9cqyy5GbWt/Ybp2xrx8Q= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0/go.mod h1:LqaApwGx/oUmzsbqxkzuBvyoPpkxk3JQWnqfVrJ3wCA= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.55.0 h1:sqmsIQ75l6lfZjjpnXXT9DFVtYEDg6CH0/Cn4/3A1Wg= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.55.0/go.mod h1:rsg1EO8LXSs2po50PB5CeY/MSVlhghuKBgXlKnqm6ks= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= @@ -355,16 +355,16 @@ go.opentelemetry.io/contrib/samplers/jaegerremote v0.23.0/go.mod h1:1kbAgQa5lgYC go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 h1:nSiV3s7wiCam610XcLbYOmMfJxB9gO4uK3Xgv5gmTgg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0/go.mod h1:hKn/e/Nmd19/x1gvIHwtOwVWM+VhuITSWip3JUDghj0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 h1:m0yTiGDLUvVYaTFbAvCkVYIYcvwKt3G7OLoN77NUs/8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0/go.mod h1:wBQbT4UekBfegL2nx0Xk1vBcnzyBPsIVm9hRG4fYcr4= go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= +go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= @@ -386,8 +386,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -462,8 +462,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk= golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= -gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= +gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0= +gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -471,18 +471,18 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 h1:CT2Thj5AuPV9phrYMtzX11k+XkzMGfRAet42PmoTATM= google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988/go.mod h1:7uvplUBj4RjHAxIZ//98LzOvrQ04JBkaixRmCMI29hc= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= -google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= +google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/api/folder.go b/pkg/api/folder.go index 12b2e6b2d41..c9f2963f120 100644 --- a/pkg/api/folder.go +++ b/pkg/api/folder.go @@ -20,6 +20,7 @@ import ( "github.com/grafana/grafana/pkg/api/routing" folderalpha1 "github.com/grafana/grafana/pkg/apis/folder/v0alpha1" "github.com/grafana/grafana/pkg/infra/metrics" + "github.com/grafana/grafana/pkg/infra/slugify" internalfolders "github.com/grafana/grafana/pkg/registry/apis/folders" "github.com/grafana/grafana/pkg/services/accesscontrol" grafanaapiserver "github.com/grafana/grafana/pkg/services/apiserver" @@ -635,8 +636,6 @@ type folderK8sHandler struct { // #TODO check if it makes more sense to move this to FolderAPIBuilder accesscontrolService accesscontrol.Service userService user.Service - // #TODO remove after we handle the nested folder case - folderService folder.Service } //----------------------------------------------------------------------------------------- @@ -650,7 +649,6 @@ func newFolderK8sHandler(hs *HTTPServer) *folderK8sHandler { clientConfigProvider: hs.clientConfigProvider, accesscontrolService: hs.accesscontrolService, userService: hs.userService, - folderService: hs.folderService, } } @@ -884,55 +882,38 @@ func (fk8s *folderK8sHandler) newToFolderDto(c *contextmodel.ReqContext, item un return dtos.Folder{}, err } - parents := []*folder.Folder{} - if folderDTO.ParentUID != "" { - parents, err = fk8s.folderService.GetParents( - c.Req.Context(), - folder.GetParentsQuery{ - UID: folderDTO.UID, - OrgID: folderDTO.OrgID, - }) - if err != nil { - return dtos.Folder{}, err - } + if len(f.Fullpath) == 0 || len(f.FullpathUIDs) == 0 { + return folderDTO, nil } - // #TODO refactor so that we have just one function for converting to folder DTO - toParentDTO := func(fold *folder.Folder, checkCanView bool) (dtos.Folder, error) { - g, err := guardian.NewByFolder(c.Req.Context(), fold, c.SignedInUser.GetOrgID(), c.SignedInUser) - if err != nil { - return dtos.Folder{}, err - } + parentsFullPath, err := internalfolders.GetParentTitles(f.Fullpath) + if err != nil { + return dtos.Folder{}, err + } + parentsFullPathUIDs := strings.Split(f.FullpathUIDs, "/") - if checkCanView { - canView, _ := g.CanView() - if !canView { - return dtos.Folder{ - UID: REDACTED, - Title: REDACTED, - }, nil - } - } - metrics.MFolderIDsAPICount.WithLabelValues(metrics.NewToFolderDTO).Inc() - - return dtos.Folder{ - UID: fold.UID, - Title: fold.Title, - URL: fold.URL, - }, nil + // The first part of the path is the newly created folder which we don't need to include + // in the parents field + if len(parentsFullPath) < 2 || len(parentsFullPathUIDs) < 2 { + return folderDTO, nil } - folderDTO.Parents = make([]dtos.Folder, 0, len(parents)) - for _, f := range parents { - DTO, err := toParentDTO(f, true) - if err != nil { - // #TODO add logging - // fk8s.log.Error("failed to convert folder to DTO", "folder", f.UID, "org", f.OrgID, "error", err) - continue - } - folderDTO.Parents = append(folderDTO.Parents, DTO) + parents := []dtos.Folder{} + for i, v := range parentsFullPath[1:] { + slug := slugify.Slugify(v) + uid := parentsFullPathUIDs[1:][i] + url := dashboards.GetFolderURL(uid, slug) + + parents = append(parents, dtos.Folder{ + UID: uid, + OrgID: c.SignedInUser.GetOrgID(), + Title: v, + URL: url, + }) } + folderDTO.Parents = parents + return folderDTO, nil } @@ -953,23 +934,19 @@ func (fk8s *folderK8sHandler) getFolderACMetadata(c *contextmodel.ReqContext, f return nil, nil } - var err error - parents := []*folder.Folder{} - if f.ParentUID != "" { - parents, err = fk8s.folderService.GetParents( - c.Req.Context(), - folder.GetParentsQuery{ - UID: f.UID, - OrgID: c.SignedInUser.GetOrgID(), - }) - if err != nil { - return nil, err - } + if len(f.FullpathUIDs) == 0 { + return map[string]bool{}, nil + } + + parentsFullPathUIDs := strings.Split(f.FullpathUIDs, "/") + // The first part of the path is the newly created folder which we don't need to check here + if len(parentsFullPathUIDs) < 2 { + return map[string]bool{}, nil } folderIDs := map[string]bool{f.UID: true} - for _, p := range parents { - folderIDs[p.UID] = true + for _, uid := range parentsFullPathUIDs[1:] { + folderIDs[uid] = true } allMetadata := getMultiAccessControlMetadata(c, dashboards.ScopeFoldersPrefix, folderIDs) diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index 481dbeb1b39..f0ab30ee9c0 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -39,8 +39,8 @@ require ( golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.25.0 // indirect golang.org/x/text v0.18.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.66.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/grpc v1.67.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index 3e4397d878d..f2a1732dde0 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -135,10 +135,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= -google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= +google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/apimachinery/utils/meta.go b/pkg/apimachinery/utils/meta.go index 3c16efc81ca..b0074c3eaf2 100644 --- a/pkg/apimachinery/utils/meta.go +++ b/pkg/apimachinery/utils/meta.go @@ -34,6 +34,11 @@ const AnnoKeyOriginPath = "grafana.app/originPath" const AnnoKeyOriginHash = "grafana.app/originHash" const AnnoKeyOriginTimestamp = "grafana.app/originTimestamp" +// #TODO revisit keeping these folder-specific annotations once we have complete support for mode 1 + +const AnnoKeyFullPath = "grafana.app/fullPath" +const AnnoKeyFullPathUIDs = "grafana.app/fullPathUIDs" + // ResourceOriginInfo is saved in annotations. This is used to identify where the resource came from // This object can model the same data as our existing provisioning table or a more general git sync type ResourceOriginInfo struct { @@ -101,6 +106,11 @@ type GrafanaMetaAccessor interface { // NOTE the type must match the existing value, or an error will be thrown SetStatus(any) error + GetFullPath() string + SetFullPath(path string) + GetFullPathUIDs() string + SetFullPathUIDs(path string) + // Find a title in the object // This will reflect the object and try to get: // * spec.title @@ -598,6 +608,22 @@ func (m *grafanaMetaAccessor) SetStatus(s any) (err error) { return } +func (m *grafanaMetaAccessor) GetFullPath() string { + return m.get(AnnoKeyFullPath) +} + +func (m *grafanaMetaAccessor) SetFullPath(path string) { + m.SetAnnotation(AnnoKeyFullPath, path) +} + +func (m *grafanaMetaAccessor) GetFullPathUIDs() string { + return m.get(AnnoKeyFullPathUIDs) +} + +func (m *grafanaMetaAccessor) SetFullPathUIDs(path string) { + m.SetAnnotation(AnnoKeyFullPathUIDs, path) +} + func (m *grafanaMetaAccessor) FindTitle(defaultTitle string) string { // look for Spec.Title or Spec.Name spec := m.r.FieldByName("Spec") diff --git a/pkg/apis/iam/v0alpha1/types_team.go b/pkg/apis/iam/v0alpha1/types_team.go index 9def089d78f..eff4e770f84 100644 --- a/pkg/apis/iam/v0alpha1/types_team.go +++ b/pkg/apis/iam/v0alpha1/types_team.go @@ -1,6 +1,8 @@ package v0alpha1 import ( + "fmt" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -15,6 +17,13 @@ type Team struct { type TeamSpec struct { Title string `json:"title,omitempty"` Email string `json:"email,omitempty"` + + // This is currently used for authorization checks but we don't want to expose it + InternalID int64 `json:"-"` +} + +func (t Team) AuthID() string { + return fmt.Sprintf("%d", t.Spec.InternalID) } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index 4ec79bb6de8..3a728b84ae9 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -6,7 +6,7 @@ require ( github.com/google/go-cmp v0.6.0 github.com/grafana/authlib/claims v0.0.0-20240903121118-16441568af1e github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1 - github.com/prometheus/client_golang v1.20.3 + github.com/prometheus/client_golang v1.20.4 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/propagators/jaeger v1.30.0 go.opentelemetry.io/otel v1.30.0 @@ -64,16 +64,16 @@ require ( go.etcd.io/etcd/api/v3 v3.5.14 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.14 // indirect go.etcd.io/etcd/client/v3 v3.5.14 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 // indirect go.opentelemetry.io/otel/metric v1.30.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect + go.opentelemetry.io/otel/sdk v1.30.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect + golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sys v0.25.0 // indirect @@ -82,9 +82,9 @@ require ( golang.org/x/time v0.6.0 // indirect golang.org/x/tools v0.24.0 // indirect google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.66.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/grpc v1.67.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index ca022eacc5f..73455d71aea 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -132,8 +132,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN 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/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= -github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4= -github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= +github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= 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= @@ -188,22 +188,22 @@ go.etcd.io/etcd/raft/v3 v3.5.13 h1:7r/NKAOups1YnKcfro2RvGGo2PTuizF/xh26Z2CTAzA= go.etcd.io/etcd/raft/v3 v3.5.13/go.mod h1:uUFibGLn2Ksm2URMxN1fICGhk8Wu96EfDQyuLhAcAmw= go.etcd.io/etcd/server/v3 v3.5.13 h1:V6KG+yMfMSqWt+lGnhFpP5z5dRUj1BDRJ5k1fQ9DFok= go.etcd.io/etcd/server/v3 v3.5.13/go.mod h1:K/8nbsGupHqmr5MkgaZpLlH1QdX1pcNQLAkODy44XcQ= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 h1:hCq2hNMwsegUvPzI7sPOvtO9cqyy5GbWt/Ybp2xrx8Q= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0/go.mod h1:LqaApwGx/oUmzsbqxkzuBvyoPpkxk3JQWnqfVrJ3wCA= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= go.opentelemetry.io/contrib/propagators/jaeger v1.30.0 h1:g8+Y+7lnhH1DB0THjPPthzQ+RlzAntmTz8+TH2sRU0k= go.opentelemetry.io/contrib/propagators/jaeger v1.30.0/go.mod h1:lRMaD/FjOQJ2yz/MwOHYxP/BTCMFodNW/wuYDkJvdA4= go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 h1:nSiV3s7wiCam610XcLbYOmMfJxB9gO4uK3Xgv5gmTgg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0/go.mod h1:hKn/e/Nmd19/x1gvIHwtOwVWM+VhuITSWip3JUDghj0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 h1:m0yTiGDLUvVYaTFbAvCkVYIYcvwKt3G7OLoN77NUs/8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0/go.mod h1:wBQbT4UekBfegL2nx0Xk1vBcnzyBPsIVm9hRG4fYcr4= go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= +go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= @@ -224,8 +224,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -290,18 +290,18 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 h1:CT2Thj5AuPV9phrYMtzX11k+XkzMGfRAet42PmoTATM= google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988/go.mod h1:7uvplUBj4RjHAxIZ//98LzOvrQ04JBkaixRmCMI29hc= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= -google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= +google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/build/go.mod b/pkg/build/go.mod index eb21e9948a2..2d855a277ba 100644 --- a/pkg/build/go.mod +++ b/pkg/build/go.mod @@ -20,7 +20,7 @@ require ( github.com/Masterminds/semver/v3 v3.2.0 // @grafana/grafana-release-guild github.com/aws/aws-sdk-go v1.55.5 // @grafana/aws-datasources github.com/blang/semver/v4 v4.0.0 // @grafana/grafana-release-guild - github.com/docker/docker v26.0.2+incompatible // @grafana/grafana-release-guild + github.com/docker/docker v27.3.1+incompatible // @grafana/grafana-release-guild github.com/drone/drone-cli v1.6.1 // @grafana/grafana-release-guild github.com/gogo/protobuf v1.3.2 // indirect; @grafana/alerting-backend github.com/google/go-cmp v0.6.0 // @grafana/grafana-backend-group @@ -32,9 +32,9 @@ require ( github.com/stretchr/testify v1.9.0 // @grafana/grafana-backend-group github.com/urfave/cli v1.22.15 // @grafana/grafana-backend-group github.com/urfave/cli/v2 v2.27.1 // @grafana/grafana-backend-group - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect; @grafana/plugins-platform-backend + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 // indirect; @grafana/plugins-platform-backend go.opentelemetry.io/otel v1.30.0 // indirect; @grafana/grafana-backend-group - go.opentelemetry.io/otel/sdk v1.29.0 // indirect; @grafana/grafana-backend-group + go.opentelemetry.io/otel/sdk v1.30.0 // indirect; @grafana/grafana-backend-group go.opentelemetry.io/otel/trace v1.30.0 // indirect; @grafana/grafana-backend-group golang.org/x/crypto v0.27.0 // indirect; @grafana/grafana-backend-group golang.org/x/mod v0.20.0 // @grafana/grafana-backend-group @@ -45,7 +45,7 @@ require ( golang.org/x/time v0.6.0 // indirect; @grafana/grafana-backend-group golang.org/x/tools v0.24.0 // indirect; @grafana/grafana-as-code google.golang.org/api v0.191.0 // @grafana/grafana-backend-group - google.golang.org/grpc v1.66.0 // indirect; @grafana/plugins-platform-backend + google.golang.org/grpc v1.67.0 // indirect; @grafana/plugins-platform-backend google.golang.org/protobuf v1.34.2 // indirect; @grafana/plugins-platform-backend gopkg.in/yaml.v3 v3.0.1 // @grafana/alerting-backend ) @@ -87,8 +87,8 @@ require ( go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect golang.org/x/sys v0.25.0 // indirect google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 // indirect; @grafana/grafana-backend-group - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) @@ -110,13 +110,13 @@ require ( github.com/vektah/gqlparser/v2 v2.5.11 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240518090000-14441aefdf88 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.2.0-alpha // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 // indirect go.opentelemetry.io/otel/log v0.2.0-alpha // indirect go.opentelemetry.io/otel/sdk/log v0.2.0-alpha // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect - golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect + golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect gotest.tools/v3 v3.5.1 // indirect ) diff --git a/pkg/build/go.sum b/pkg/build/go.sum index e348659ac5e..3e6e7758deb 100644 --- a/pkg/build/go.sum +++ b/pkg/build/go.sum @@ -227,8 +227,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 h1:hCq2hNMwsegUvPzI7sPOvtO9cqyy5GbWt/Ybp2xrx8Q= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0/go.mod h1:LqaApwGx/oUmzsbqxkzuBvyoPpkxk3JQWnqfVrJ3wCA= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= @@ -237,18 +237,18 @@ go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-2024051809000 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240518090000-14441aefdf88/go.mod h1:JGG8ebaMO5nXOPnvKEl+DiA4MGwFjCbjsxT1WHIEBPY= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.2.0-alpha h1:z2s6Zba+OUyayRv5m1AXWNUTGh57K1iMhy6emU5QT5Y= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.2.0-alpha/go.mod h1:paOXXyUgPW6jYxYkP0pB47H2zHE1fPvMJ4E4G9LHOi0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 h1:nSiV3s7wiCam610XcLbYOmMfJxB9gO4uK3Xgv5gmTgg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0/go.mod h1:hKn/e/Nmd19/x1gvIHwtOwVWM+VhuITSWip3JUDghj0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 h1:m0yTiGDLUvVYaTFbAvCkVYIYcvwKt3G7OLoN77NUs/8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0/go.mod h1:wBQbT4UekBfegL2nx0Xk1vBcnzyBPsIVm9hRG4fYcr4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 h1:JAv0Jwtl01UFiyWZEMiJZBiTlv5A50zNs8lsthXqIio= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0/go.mod h1:QNKLmUEAq2QUbPQUfvw4fmv0bgbK7UlOSFCnXyfvSNc= go.opentelemetry.io/otel/log v0.2.0-alpha h1:ixOPvMzserpqA07SENHvRzkZOsnG0XbPr74hv1AQ+n0= go.opentelemetry.io/otel/log v0.2.0-alpha/go.mod h1:vbFZc65yq4c4ssvXY43y/nIqkNJLxORrqw0L85P59LA= go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= +go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= go.opentelemetry.io/otel/sdk/log v0.2.0-alpha h1:jGTkL/jroJ31jnP6jDl34N/mDOfRGGYZHcHsCM+5kWA= go.opentelemetry.io/otel/sdk/log v0.2.0-alpha/go.mod h1:Hd8Lw9FPGUM3pfY7iGMRvFaC2Nyau4Ajb5WnQ9OdIho= go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= @@ -266,8 +266,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -339,18 +339,18 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 h1:CT2Thj5AuPV9phrYMtzX11k+XkzMGfRAet42PmoTATM= google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988/go.mod h1:7uvplUBj4RjHAxIZ//98LzOvrQ04JBkaixRmCMI29hc= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= -google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= +google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/pkg/plugins/backendplugin/backendplugin.go b/pkg/plugins/backendplugin/backendplugin.go index 2d8f6de2c27..baf755f88fa 100644 --- a/pkg/plugins/backendplugin/backendplugin.go +++ b/pkg/plugins/backendplugin/backendplugin.go @@ -3,7 +3,8 @@ package backendplugin import ( "github.com/grafana/grafana/pkg/plugins/log" + "go.opentelemetry.io/otel/trace" ) // PluginFactoryFunc is a function type for creating a Plugin. -type PluginFactoryFunc func(pluginID string, logger log.Logger, env func() []string) (Plugin, error) +type PluginFactoryFunc func(pluginID string, logger log.Logger, tracer trace.Tracer, env func() []string) (Plugin, error) diff --git a/pkg/plugins/backendplugin/coreplugin/core_plugin.go b/pkg/plugins/backendplugin/coreplugin/core_plugin.go index 6010b2650c5..0cb85b38c89 100644 --- a/pkg/plugins/backendplugin/coreplugin/core_plugin.go +++ b/pkg/plugins/backendplugin/coreplugin/core_plugin.go @@ -4,6 +4,7 @@ import ( "context" "github.com/grafana/grafana-plugin-sdk-go/backend" + "go.opentelemetry.io/otel/trace" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin" @@ -24,7 +25,7 @@ type corePlugin struct { // New returns a new backendplugin.PluginFactoryFunc for creating a core (built-in) backendplugin.Plugin. func New(opts backend.ServeOpts) backendplugin.PluginFactoryFunc { - return func(pluginID string, logger log.Logger, _ func() []string) (backendplugin.Plugin, error) { + return func(pluginID string, logger log.Logger, _ trace.Tracer, _ func() []string) (backendplugin.Plugin, error) { return &corePlugin{ pluginID: pluginID, logger: logger, diff --git a/pkg/plugins/backendplugin/coreplugin/core_plugin_test.go b/pkg/plugins/backendplugin/coreplugin/core_plugin_test.go index c3243c52ae9..6b4cc5a385a 100644 --- a/pkg/plugins/backendplugin/coreplugin/core_plugin_test.go +++ b/pkg/plugins/backendplugin/coreplugin/core_plugin_test.go @@ -8,13 +8,14 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin" "github.com/grafana/grafana/pkg/plugins/log" + "github.com/grafana/grafana/pkg/plugins/manager/fakes" "github.com/stretchr/testify/require" ) func TestCorePlugin(t *testing.T) { t.Run("New core plugin with empty opts should return expected values", func(t *testing.T) { factory := coreplugin.New(backend.ServeOpts{}) - p, err := factory("plugin", log.New("test"), nil) + p, err := factory("plugin", log.New("test"), fakes.InitializeNoopTracerForTest(), nil) require.NoError(t, err) require.NotNil(t, p) require.NoError(t, p.Start(context.Background())) @@ -47,7 +48,7 @@ func TestCorePlugin(t *testing.T) { return nil }), }) - p, err := factory("plugin", log.New("test"), nil) + p, err := factory("plugin", log.New("test"), fakes.InitializeNoopTracerForTest(), nil) require.NoError(t, err) require.NotNil(t, p) require.NoError(t, p.Start(context.Background())) diff --git a/pkg/plugins/backendplugin/coreplugin/registry.go b/pkg/plugins/backendplugin/coreplugin/registry.go index 23dc4524f75..423d74ac29d 100644 --- a/pkg/plugins/backendplugin/coreplugin/registry.go +++ b/pkg/plugins/backendplugin/coreplugin/registry.go @@ -254,7 +254,7 @@ func NewPlugin(pluginID string, cfg *setting.Cfg, httpClientProvider *httpclient if backendFactory == nil { return nil, ErrCorePluginNotFound } - bp, err := backendFactory(p.ID, p.Logger(), nil) + bp, err := backendFactory(p.ID, p.Logger(), tracer, nil) if err != nil { return nil, err } diff --git a/pkg/plugins/backendplugin/grpcplugin/client.go b/pkg/plugins/backendplugin/grpcplugin/client.go index 5ce776ec757..35920fef2cf 100644 --- a/pkg/plugins/backendplugin/grpcplugin/client.go +++ b/pkg/plugins/backendplugin/grpcplugin/client.go @@ -6,6 +6,8 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin" goplugin "github.com/hashicorp/go-plugin" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + trace "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/embedded" "google.golang.org/grpc" "github.com/grafana/grafana/pkg/plugins/backendplugin" @@ -40,7 +42,20 @@ var pluginSet = map[int]goplugin.PluginSet{ }, } -func newClientConfig(executablePath string, args []string, env []string, skipHostEnvVars bool, logger log.Logger, +type clientTracerProvider struct { + tracer trace.Tracer + embedded.TracerProvider +} + +func (ctp *clientTracerProvider) Tracer(instrumentationName string, opts ...trace.TracerOption) trace.Tracer { + return ctp.tracer +} + +func newClientTracerProvider(tracer trace.Tracer) trace.TracerProvider { + return &clientTracerProvider{tracer: tracer} +} + +func newClientConfig(executablePath string, args []string, env []string, skipHostEnvVars bool, logger log.Logger, tracer trace.Tracer, versionedPlugins map[int]goplugin.PluginSet) *goplugin.ClientConfig { // We can ignore gosec G201 here, since the dynamic part of executablePath comes from the plugin definition // nolint:gosec @@ -55,7 +70,13 @@ func newClientConfig(executablePath string, args []string, env []string, skipHos Logger: logWrapper{Logger: logger}, AllowedProtocols: []goplugin.Protocol{goplugin.ProtocolGRPC}, GRPCDialOptions: []grpc.DialOption{ - grpc.WithStatsHandler(otelgrpc.NewClientHandler()), + // https://github.com/grafana/app-platform-wg/issues/140 + // external plugins are loaded before k8s API server + // configures the tracing service thus failing to + // record trace span in the middleware. + // With code below we are passing the same tracer that k8s API server + // uses so that middleware is configured with tracer. + grpc.WithStatsHandler(otelgrpc.NewClientHandler(otelgrpc.WithTracerProvider(newClientTracerProvider(tracer)))), }, } } diff --git a/pkg/plugins/backendplugin/grpcplugin/client_proto.go b/pkg/plugins/backendplugin/grpcplugin/client_proto.go index 19c83d0c7dd..9922258f9e2 100644 --- a/pkg/plugins/backendplugin/grpcplugin/client_proto.go +++ b/pkg/plugins/backendplugin/grpcplugin/client_proto.go @@ -4,6 +4,7 @@ import ( "context" "errors" + trace "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2" @@ -44,6 +45,7 @@ type ProtoClientOpts struct { ExecutableArgs []string Env []string Logger log.Logger + Tracer trace.Tracer } func NewProtoClient(opts ProtoClientOpts) (ProtoClient, error) { @@ -56,6 +58,7 @@ func NewProtoClient(opts ProtoClientOpts) (ProtoClient, error) { versionedPlugins: pluginSet, }, opts.Logger, + opts.Tracer, func() []string { return opts.Env }, ) diff --git a/pkg/plugins/backendplugin/grpcplugin/grpc_plugin.go b/pkg/plugins/backendplugin/grpcplugin/grpc_plugin.go index bce188928aa..d1c6152a0ef 100644 --- a/pkg/plugins/backendplugin/grpcplugin/grpc_plugin.go +++ b/pkg/plugins/backendplugin/grpcplugin/grpc_plugin.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/hashicorp/go-plugin" + trace "go.opentelemetry.io/otel/trace" "github.com/grafana/grafana/pkg/infra/process" "github.com/grafana/grafana/pkg/plugins" @@ -37,17 +38,17 @@ const ( // newPlugin allocates and returns a new gRPC (external) backendplugin.Plugin. func newPlugin(descriptor PluginDescriptor) backendplugin.PluginFactoryFunc { - return func(pluginID string, logger log.Logger, env func() []string) (backendplugin.Plugin, error) { - return newGrpcPlugin(descriptor, logger, env), nil + return func(pluginID string, logger log.Logger, tracer trace.Tracer, env func() []string) (backendplugin.Plugin, error) { + return newGrpcPlugin(descriptor, logger, tracer, env), nil } } -func newGrpcPlugin(descriptor PluginDescriptor, logger log.Logger, env func() []string) *grpcPlugin { +func newGrpcPlugin(descriptor PluginDescriptor, logger log.Logger, tracer trace.Tracer, env func() []string) *grpcPlugin { return &grpcPlugin{ descriptor: descriptor, logger: logger, clientFactory: func() *plugin.Client { - return plugin.NewClient(newClientConfig(descriptor.executablePath, descriptor.executableArgs, env(), descriptor.skipHostEnvVars, logger, descriptor.versionedPlugins)) + return plugin.NewClient(newClientConfig(descriptor.executablePath, descriptor.executableArgs, env(), descriptor.skipHostEnvVars, logger, tracer, descriptor.versionedPlugins)) }, state: pluginStateNotStarted, } diff --git a/pkg/plugins/manager/fakes/fakes.go b/pkg/plugins/manager/fakes/fakes.go index 2ba324f311d..f25daddc504 100644 --- a/pkg/plugins/manager/fakes/fakes.go +++ b/pkg/plugins/manager/fakes/fakes.go @@ -8,6 +8,8 @@ import ( "sync" "github.com/grafana/grafana-plugin-sdk-go/backend" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/auth" @@ -267,6 +269,14 @@ func (r *FakePluginRepo) PluginVersion(ctx context.Context, pluginID, version st return repo.VersionData{}, nil } +type fakeTracerProvider struct { + noop.TracerProvider +} + +func InitializeNoopTracerForTest() trace.Tracer { + return fakeTracerProvider{}.Tracer("test") +} + type FakePluginStorage struct { ExtractFunc func(_ context.Context, pluginID string, dirNameFunc storage.DirNameGeneratorFunc, z *zip.ReadCloser) (*storage.ExtractedPluginArchive, error) } @@ -340,7 +350,7 @@ func NewFakeBackendProcessProvider() *FakeBackendProcessProvider { } f.BackendFactoryFunc = func(ctx context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc { f.Requested[p.ID]++ - return func(pluginID string, _ log.Logger, _ func() []string) (backendplugin.Plugin, error) { + return func(pluginID string, _ log.Logger, _ trace.Tracer, _ func() []string) (backendplugin.Plugin, error) { f.Invoked[pluginID]++ return &FakePluginClient{}, nil } diff --git a/pkg/plugins/manager/pipeline/initialization/steps.go b/pkg/plugins/manager/pipeline/initialization/steps.go index 2df09d1144a..02573f6ba20 100644 --- a/pkg/plugins/manager/pipeline/initialization/steps.go +++ b/pkg/plugins/manager/pipeline/initialization/steps.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/grafana/pkg/plugins/log" "github.com/grafana/grafana/pkg/plugins/manager/process" "github.com/grafana/grafana/pkg/plugins/manager/registry" + "go.opentelemetry.io/otel/trace" ) // BackendClientInit implements an InitializeFunc for initializing a backend plugin process. @@ -21,20 +22,22 @@ type BackendClientInit struct { envVarProvider envvars.Provider backendProvider plugins.BackendFactoryProvider log log.Logger + tracer trace.Tracer } // BackendClientInitStep returns a new InitializeFunc for registering a backend plugin process. func BackendClientInitStep(envVarProvider envvars.Provider, - backendProvider plugins.BackendFactoryProvider) InitializeFunc { - return newBackendProcessRegistration(envVarProvider, backendProvider).Initialize + backendProvider plugins.BackendFactoryProvider, tracer trace.Tracer) InitializeFunc { + return newBackendProcessRegistration(envVarProvider, backendProvider, tracer).Initialize } func newBackendProcessRegistration(envVarProvider envvars.Provider, - backendProvider plugins.BackendFactoryProvider) *BackendClientInit { + backendProvider plugins.BackendFactoryProvider, tracer trace.Tracer) *BackendClientInit { return &BackendClientInit{ backendProvider: backendProvider, envVarProvider: envVarProvider, log: log.New("plugins.backend.registration"), + tracer: tracer, } } @@ -49,7 +52,7 @@ func (b *BackendClientInit) Initialize(ctx context.Context, p *plugins.Plugin) ( // this will ensure that the env variables are calculated every time a plugin is started envFunc := func() []string { return b.envVarProvider.PluginEnvVars(ctx, p) } - if backendClient, err := backendFactory(p.ID, p.Logger(), envFunc); err != nil { + if backendClient, err := backendFactory(p.ID, p.Logger(), b.tracer, envFunc); err != nil { return nil, err } else { p.RegisterClient(backendClient) diff --git a/pkg/plugins/manager/pipeline/initialization/steps_test.go b/pkg/plugins/manager/pipeline/initialization/steps_test.go index b5e43195e71..da00444e93e 100644 --- a/pkg/plugins/manager/pipeline/initialization/steps_test.go +++ b/pkg/plugins/manager/pipeline/initialization/steps_test.go @@ -4,11 +4,12 @@ import ( "context" "testing" - "github.com/stretchr/testify/require" - "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/log" + "github.com/grafana/grafana/pkg/plugins/manager/fakes" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace" ) func TestInitializer_Initialize(t *testing.T) { @@ -28,7 +29,7 @@ func TestInitializer_Initialize(t *testing.T) { Class: plugins.ClassCore, } - stepFunc := BackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{plugin: p}) + stepFunc := BackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{plugin: p}, fakes.InitializeNoopTracerForTest()) var err error p, err = stepFunc(context.Background(), p) @@ -52,7 +53,7 @@ func TestInitializer_Initialize(t *testing.T) { Class: plugins.ClassExternal, } - stepFunc := BackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{plugin: p}) + stepFunc := BackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{plugin: p}, fakes.InitializeNoopTracerForTest()) var err error p, err = stepFunc(context.Background(), p) @@ -76,7 +77,7 @@ func TestInitializer_Initialize(t *testing.T) { Class: plugins.ClassExternal, } - stepFunc := BackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{plugin: p}) + stepFunc := BackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{plugin: p}, fakes.InitializeNoopTracerForTest()) var err error p, err = stepFunc(context.Background(), p) @@ -96,7 +97,7 @@ func TestInitializer_Initialize(t *testing.T) { i := BackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{ plugin: p, - }) + }, fakes.InitializeNoopTracerForTest()) var err error p, err = i(context.Background(), p) @@ -115,7 +116,7 @@ type fakeBackendProvider struct { } func (f *fakeBackendProvider) BackendFactory(_ context.Context, _ *plugins.Plugin) backendplugin.PluginFactoryFunc { - return func(_ string, _ log.Logger, _ func() []string) (backendplugin.Plugin, error) { + return func(_ string, _ log.Logger, _ trace.Tracer, _ func() []string) (backendplugin.Plugin, error) { return f.plugin, nil } } diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index 8d79b5ff460..51e5bc9cc60 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -7,13 +7,13 @@ require ( github.com/grafana/grafana-plugin-sdk-go v0.251.0 github.com/json-iterator/go v1.1.12 github.com/patrickmn/go-cache v2.1.0+incompatible - github.com/prometheus/client_golang v1.20.3 + github.com/prometheus/client_golang v1.20.4 github.com/prometheus/common v0.55.0 github.com/prometheus/prometheus v1.8.2-0.20221021121301-51a44e6657c3 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.30.0 go.opentelemetry.io/otel/trace v1.30.0 - golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa + golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e k8s.io/apimachinery v0.31.1 ) @@ -101,14 +101,14 @@ require ( github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.55.0 // indirect go.opentelemetry.io/contrib/propagators/jaeger v1.30.0 // indirect go.opentelemetry.io/contrib/samplers/jaegerremote v0.23.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 // indirect go.opentelemetry.io/otel/metric v1.30.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect + go.opentelemetry.io/otel/sdk v1.30.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/goleak v1.3.0 // indirect @@ -119,10 +119,10 @@ require ( golang.org/x/text v0.18.0 // indirect golang.org/x/tools v0.24.0 // indirect golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect - gonum.org/v1/gonum v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.66.0 // indirect + gonum.org/v1/gonum v0.15.1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/grpc v1.67.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index 2d878a7363f..af656a4d453 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -199,8 +199,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE 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/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4= -github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= +github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= 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.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= @@ -262,8 +262,8 @@ 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.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 h1:hCq2hNMwsegUvPzI7sPOvtO9cqyy5GbWt/Ybp2xrx8Q= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0/go.mod h1:LqaApwGx/oUmzsbqxkzuBvyoPpkxk3JQWnqfVrJ3wCA= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.55.0 h1:sqmsIQ75l6lfZjjpnXXT9DFVtYEDg6CH0/Cn4/3A1Wg= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.55.0/go.mod h1:rsg1EO8LXSs2po50PB5CeY/MSVlhghuKBgXlKnqm6ks= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= @@ -275,16 +275,16 @@ go.opentelemetry.io/contrib/samplers/jaegerremote v0.23.0/go.mod h1:1kbAgQa5lgYC go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 h1:nSiV3s7wiCam610XcLbYOmMfJxB9gO4uK3Xgv5gmTgg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0/go.mod h1:hKn/e/Nmd19/x1gvIHwtOwVWM+VhuITSWip3JUDghj0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 h1:m0yTiGDLUvVYaTFbAvCkVYIYcvwKt3G7OLoN77NUs/8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0/go.mod h1:wBQbT4UekBfegL2nx0Xk1vBcnzyBPsIVm9hRG4fYcr4= go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= +go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= @@ -297,8 +297,8 @@ go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= @@ -349,14 +349,14 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk= golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= -gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= -google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0= +gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= +google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/registry/apis/folders/conversions.go b/pkg/registry/apis/folders/conversions.go index 0deabc23ed3..31e8c051f2b 100644 --- a/pkg/registry/apis/folders/conversions.go +++ b/pkg/registry/apis/folders/conversions.go @@ -2,6 +2,7 @@ package folders import ( "fmt" + "regexp" "strconv" "time" @@ -85,10 +86,12 @@ func UnstructuredToLegacyFolder(item unstructured.Unstructured, orgID int64) *fo // #TODO add created by field if necessary // CreatedBy: meta.GetCreatedBy(), // UpdatedBy: meta.GetCreatedBy(), - URL: getURL(meta, title), - Created: createdTime, - Updated: createdTime, - OrgID: orgID, + URL: getURL(meta, title), + Created: createdTime, + Updated: createdTime, + OrgID: orgID, + Fullpath: meta.GetFullPath(), + FullpathUIDs: meta.GetFullPathUIDs(), } return f } @@ -183,6 +186,12 @@ func convertToK8sResource(v *folder.Folder, namespacer request.NamespaceMapper) if v.ParentUID != "" { meta.SetFolder(v.ParentUID) } + if v.Fullpath != "" { + meta.SetFullPath(v.Fullpath) + } + if v.FullpathUIDs != "" { + meta.SetFullPathUIDs(v.FullpathUIDs) + } f.UID = gapiutil.CalculateClusterWideUID(f) return f, nil } @@ -226,3 +235,22 @@ func getCreated(meta utils.GrafanaMetaAccessor) (*time.Time, error) { } return created, nil } + +func GetParentTitles(fullPath string) ([]string, error) { + // Find all forward slashes which aren't escaped + r, err := regexp.Compile(`[^\\](/)`) + if err != nil { + return nil, err + } + indices := r.FindAllStringIndex(fullPath, -1) + + var start int + titles := []string{} + for _, i := range indices { + titles = append(titles, fullPath[start:i[0]+1]) + start = i[0] + 2 + } + + titles = append(titles, fullPath[start:]) + return titles, nil +} diff --git a/pkg/registry/apis/folders/conversions_test.go b/pkg/registry/apis/folders/conversions_test.go new file mode 100644 index 00000000000..70310d50968 --- /dev/null +++ b/pkg/registry/apis/folders/conversions_test.go @@ -0,0 +1,18 @@ +package folders + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetParentTitles(t *testing.T) { + path := "get\\/folder-folder-0/get\\/folder-folder-1/another" + + titles, err := GetParentTitles(path) + require.Nil(t, err) + require.Equal(t, 3, len(titles)) + require.Equal(t, "get\\/folder-folder-0", titles[0]) + require.Equal(t, "get\\/folder-folder-1", titles[1]) + require.Equal(t, "another", titles[2]) +} diff --git a/pkg/registry/apis/iam/authorizer.go b/pkg/registry/apis/iam/authorizer.go index 8cc8cb68aba..6ea63508d4c 100644 --- a/pkg/registry/apis/iam/authorizer.go +++ b/pkg/registry/apis/iam/authorizer.go @@ -54,6 +54,19 @@ func newLegacyAuthorizer(ac accesscontrol.AccessControl, store legacy.LegacyIden return []string{fmt.Sprintf("serviceaccounts:id:%d", res.ID)}, nil }), }, + accesscontrol.ResourceAuthorizerOptions{ + Resource: iamv0.TeamResourceInfo.GetName(), + Attr: "id", + Resolver: accesscontrol.ResourceResolverFunc(func(ctx context.Context, ns claims.NamespaceInfo, name string) ([]string, error) { + res, err := store.GetTeamInternalID(ctx, ns, legacy.GetTeamInternalIDQuery{ + UID: name, + }) + if err != nil { + return nil, err + } + return []string{fmt.Sprintf("teams:id:%d", res.ID)}, nil + }), + }, ) return gfauthorizer.NewResourceAuthorizer(client), client diff --git a/pkg/registry/apis/iam/legacy/sql.go b/pkg/registry/apis/iam/legacy/sql.go index 5e0e3f470ed..cdabeb6ac7c 100644 --- a/pkg/registry/apis/iam/legacy/sql.go +++ b/pkg/registry/apis/iam/legacy/sql.go @@ -22,6 +22,7 @@ type LegacyIdentityStore interface { ListServiceAccounts(ctx context.Context, ns claims.NamespaceInfo, query ListServiceAccountsQuery) (*ListServiceAccountResult, error) ListServiceAccountTokens(ctx context.Context, ns claims.NamespaceInfo, query ListServiceAccountTokenQuery) (*ListServiceAccountTokenResult, error) + GetTeamInternalID(ctx context.Context, ns claims.NamespaceInfo, query GetTeamInternalIDQuery) (*GetTeamInternalIDResult, error) ListTeams(ctx context.Context, ns claims.NamespaceInfo, query ListTeamQuery) (*ListTeamResult, error) ListTeamBindings(ctx context.Context, ns claims.NamespaceInfo, query ListTeamBindingsQuery) (*ListTeamBindingsResult, error) ListTeamMembers(ctx context.Context, ns claims.NamespaceInfo, query ListTeamMembersQuery) (*ListTeamMembersResult, error) diff --git a/pkg/registry/apis/iam/legacy/team.go b/pkg/registry/apis/iam/legacy/team.go index b5e1e5d0e12..be7cb9ab61a 100644 --- a/pkg/registry/apis/iam/legacy/team.go +++ b/pkg/registry/apis/iam/legacy/team.go @@ -3,6 +3,7 @@ package legacy import ( "context" "database/sql" + "errors" "fmt" "time" @@ -14,6 +15,79 @@ import ( "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" ) +type GetTeamInternalIDQuery struct { + OrgID int64 + UID string +} + +type GetTeamInternalIDResult struct { + ID int64 +} + +var sqlQueryTeamInternalIDTemplate = mustTemplate("team_internal_id.sql") + +func newGetTeamInternalID(sql *legacysql.LegacyDatabaseHelper, q *GetTeamInternalIDQuery) getTeamInternalIDQuery { + return getTeamInternalIDQuery{ + SQLTemplate: sqltemplate.New(sql.DialectForDriver()), + TeamTable: sql.Table("team"), + Query: q, + } +} + +type getTeamInternalIDQuery struct { + sqltemplate.SQLTemplate + TeamTable string + Query *GetTeamInternalIDQuery +} + +func (r getTeamInternalIDQuery) Validate() error { return nil } + +func (s *legacySQLStore) GetTeamInternalID( + ctx context.Context, + ns claims.NamespaceInfo, + query GetTeamInternalIDQuery, +) (*GetTeamInternalIDResult, error) { + query.OrgID = ns.OrgID + if query.OrgID == 0 { + return nil, fmt.Errorf("expected non zero org id") + } + + sql, err := s.sql(ctx) + if err != nil { + return nil, err + } + + req := newGetTeamInternalID(sql, &query) + q, err := sqltemplate.Execute(sqlQueryTeamInternalIDTemplate, req) + if err != nil { + return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeamInternalIDTemplate.Name(), err) + } + + rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...) + defer func() { + if rows != nil { + _ = rows.Close() + } + }() + + if err != nil { + return nil, err + } + + if !rows.Next() { + return nil, errors.New("team not found") + } + + var id int64 + if err := rows.Scan(&id); err != nil { + return nil, err + } + + return &GetTeamInternalIDResult{ + id, + }, nil +} + type ListTeamQuery struct { OrgID int64 UID string diff --git a/pkg/registry/apis/iam/legacy/team_internal_id.sql b/pkg/registry/apis/iam/legacy/team_internal_id.sql new file mode 100644 index 00000000000..f2f4e3e5fad --- /dev/null +++ b/pkg/registry/apis/iam/legacy/team_internal_id.sql @@ -0,0 +1,5 @@ +SELECT t.id +FROM {{ .Ident .TeamTable }} as t +WHERE t.org_id = {{ .Arg .Query.OrgID }} +AND t.uid = {{ .Arg .Query.UID }} +LIMIT 1; diff --git a/pkg/registry/apis/iam/register.go b/pkg/registry/apis/iam/register.go index 1a69dcf136c..1ef15561291 100644 --- a/pkg/registry/apis/iam/register.go +++ b/pkg/registry/apis/iam/register.go @@ -97,7 +97,7 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge storage := map[string]rest.Storage{} teamResource := iamv0.TeamResourceInfo - storage[teamResource.StoragePath()] = team.NewLegacyStore(b.store) + storage[teamResource.StoragePath()] = team.NewLegacyStore(b.store, b.accessClient) storage[teamResource.StoragePath("members")] = team.NewLegacyTeamMemberREST(b.store) teamBindingResource := iamv0.TeamBindingResourceInfo diff --git a/pkg/registry/apis/iam/team/store.go b/pkg/registry/apis/iam/team/store.go index ae6f78291fd..864e5a36df8 100644 --- a/pkg/registry/apis/iam/team/store.go +++ b/pkg/registry/apis/iam/team/store.go @@ -2,6 +2,7 @@ package team import ( "context" + "fmt" "strconv" "k8s.io/apimachinery/pkg/apis/meta/internalversion" @@ -15,6 +16,7 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/iam/common" "github.com/grafana/grafana/pkg/registry/apis/iam/legacy" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" + "github.com/grafana/grafana/pkg/services/team" ) var ( @@ -27,12 +29,13 @@ var ( var resource = iamv0.TeamResourceInfo -func NewLegacyStore(store legacy.LegacyIdentityStore) *LegacyStore { - return &LegacyStore{store} +func NewLegacyStore(store legacy.LegacyIdentityStore, ac claims.AccessClient) *LegacyStore { + return &LegacyStore{store, ac} } type LegacyStore struct { store legacy.LegacyIdentityStore + ac claims.AccessClient } func (s *LegacyStore) New() runtime.Object { @@ -58,74 +61,84 @@ func (s *LegacyStore) ConvertToTable(ctx context.Context, object runtime.Object, return resource.TableConverter().ConvertToTable(ctx, object, tableOptions) } -func (s *LegacyStore) doList(ctx context.Context, ns claims.NamespaceInfo, query legacy.ListTeamQuery) (*iamv0.TeamList, error) { - rsp, err := s.store.ListTeams(ctx, ns, query) - if err != nil { - return nil, err - } - list := &iamv0.TeamList{ - ListMeta: metav1.ListMeta{ - ResourceVersion: strconv.FormatInt(rsp.RV, 10), +func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { + res, err := common.List( + ctx, resource.GetName(), s.ac, common.PaginationFromListOptions(options), + func(ctx context.Context, ns claims.NamespaceInfo, p common.Pagination) (*common.ListResponse[iamv0.Team], error) { + found, err := s.store.ListTeams(ctx, ns, legacy.ListTeamQuery{ + Pagination: p, + }) + + if err != nil { + return nil, err + } + + teams := make([]iamv0.Team, 0, len(found.Teams)) + for _, t := range found.Teams { + teams = append(teams, toTeamObject(t, ns)) + } + + return &common.ListResponse[iamv0.Team]{ + Items: teams, + RV: found.RV, + Continue: found.Continue, + }, nil }, - } - for _, team := range rsp.Teams { - item := iamv0.Team{ - ObjectMeta: metav1.ObjectMeta{ - Name: team.UID, - Namespace: ns.Value, - CreationTimestamp: metav1.NewTime(team.Created), - ResourceVersion: strconv.FormatInt(team.Updated.UnixMilli(), 10), - }, - Spec: iamv0.TeamSpec{ - Title: team.Name, - Email: team.Email, - }, - } - meta, err := utils.MetaAccessor(&item) - if err != nil { - return nil, err - } - meta.SetUpdatedTimestamp(&team.Updated) - meta.SetOriginInfo(&utils.ResourceOriginInfo{ - Name: "SQL", - Path: strconv.FormatInt(team.ID, 10), - }) - list.Items = append(list.Items, item) + ) + + if err != nil { + return nil, fmt.Errorf("failed to list teams: %w", err) } - list.ListMeta.Continue = common.OptionalFormatInt(rsp.Continue) - list.ListMeta.ResourceVersion = common.OptionalFormatInt(rsp.RV) + list := &iamv0.TeamList{Items: res.Items} + list.ListMeta.Continue = common.OptionalFormatInt(res.Continue) + list.ListMeta.ResourceVersion = common.OptionalFormatInt(res.RV) return list, nil } -func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { - ns, err := request.NamespaceInfoFrom(ctx, true) - if err != nil { - return nil, err - } - - return s.doList(ctx, ns, legacy.ListTeamQuery{ - OrgID: ns.OrgID, - Pagination: common.PaginationFromListOptions(options), - }) -} - func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { ns, err := request.NamespaceInfoFrom(ctx, true) if err != nil { return nil, err } - rsp, err := s.doList(ctx, ns, legacy.ListTeamQuery{ + + found, err := s.store.ListTeams(ctx, ns, legacy.ListTeamQuery{ OrgID: ns.OrgID, UID: name, Pagination: common.Pagination{Limit: 1}, }) - if err != nil { - return nil, err + if found == nil || err != nil { + return nil, resource.NewNotFound(name) } - if len(rsp.Items) > 0 { - return &rsp.Items[0], nil + if len(found.Teams) < 1 { + return nil, resource.NewNotFound(name) } - return nil, resource.NewNotFound(name) + + obj := toTeamObject(found.Teams[0], ns) + return &obj, nil +} + +func toTeamObject(t team.Team, ns claims.NamespaceInfo) iamv0.Team { + obj := iamv0.Team{ + ObjectMeta: metav1.ObjectMeta{ + Name: t.UID, + Namespace: ns.Value, + CreationTimestamp: metav1.NewTime(t.Created), + ResourceVersion: strconv.FormatInt(t.Updated.UnixMilli(), 10), + }, + Spec: iamv0.TeamSpec{ + Title: t.Name, + Email: t.Email, + InternalID: t.ID, + }, + } + meta, _ := utils.MetaAccessor(&obj) + meta.SetUpdatedTimestamp(&t.Updated) + meta.SetOriginInfo(&utils.ResourceOriginInfo{ + Name: "SQL", + Path: strconv.FormatInt(t.ID, 10), + }) + + return obj } diff --git a/pkg/registry/apis/query/query.go b/pkg/registry/apis/query/query.go index e5bd2a0d4ba..dcdcb00d136 100644 --- a/pkg/registry/apis/query/query.go +++ b/pkg/registry/apis/query/query.go @@ -122,15 +122,22 @@ func (r *queryREST) Connect(connectCtx context.Context, name string, _ runtime.O // Parses the request and splits it into multiple sub queries (if necessary) req, err := b.parser.parseRequest(ctx, raw) if err != nil { + reason := metav1.StatusReasonInvalid + message := err.Error() + if errors.Is(err, datasources.ErrDataSourceNotFound) { + reason = metav1.StatusReasonNotFound // TODO, can we wrap the error somehow? - err = &errorsK8s.StatusError{ErrStatus: metav1.Status{ - Status: metav1.StatusFailure, - Code: http.StatusBadRequest, // the URL is found, but includes bad requests - Reason: metav1.StatusReasonNotFound, - Message: "datasource not found", - }} + message = "datasource not found" } + + err = &errorsK8s.StatusError{ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusBadRequest, + Reason: reason, + Message: message, + }} + responder.Error(err) return } diff --git a/pkg/registry/apis/search/register.go b/pkg/registry/apis/search/register.go index 78b2afbfd07..683f9dfbcf5 100644 --- a/pkg/registry/apis/search/register.go +++ b/pkg/registry/apis/search/register.go @@ -3,7 +3,12 @@ package search import ( "encoding/json" "net/http" + "net/url" + "strconv" + "github.com/grafana/grafana/pkg/api/response" + request2 "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" + "github.com/grafana/grafana/pkg/setting" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/authorization/authorizer" @@ -21,14 +26,17 @@ import ( var _ builder.APIGroupBuilder = (*SearchAPIBuilder)(nil) type SearchAPIBuilder struct { - unified resource.ResourceClient + unified resource.ResourceClient + namespacer request2.NamespaceMapper } func NewSearchAPIBuilder( unified resource.ResourceClient, + cfg *setting.Cfg, ) (*SearchAPIBuilder, error) { return &SearchAPIBuilder{ - unified: unified, + unified: unified, + namespacer: request2.GetNamespaceMapper(cfg), }, nil } @@ -36,11 +44,12 @@ func RegisterAPIService( features featuremgmt.FeatureToggles, apiregistration builder.APIRegistrar, unified resource.ResourceClient, + cfg *setting.Cfg, ) (*SearchAPIBuilder, error) { if !(features.IsEnabledGlobally(featuremgmt.FlagUnifiedStorageSearch) || features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs)) { return nil, nil } - builder, err := NewSearchAPIBuilder(unified) + builder, err := NewSearchAPIBuilder(unified, cfg) apiregistration.RegisterAPI(builder) return builder, err } @@ -73,17 +82,55 @@ func (b *SearchAPIBuilder) GetAPIRoutes() *builder.APIRoutes { }, }, }, - Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - urlQuery := r.URL.Query().Get("query") - searchRequest := &resource.SearchRequest{Query: urlQuery} + Handler: func(w http.ResponseWriter, r *http.Request) { + // get tenant + orgId, err := request2.OrgIDForList(r.Context()) + if err != nil { + response.Error(500, "failed to get orgId", err) + } + tenant := b.namespacer(orgId) + + queryParams, err := url.ParseQuery(r.URL.RawQuery) + if err != nil { + response.Error(500, "failed to parse query params", err) + } + + // get limit and offset from query params + limit := 0 + offset := 0 + if queryParams.Has("limit") { + limit, _ = strconv.Atoi(queryParams.Get("limit")) + } + if queryParams.Has("offset") { + offset, _ = strconv.Atoi(queryParams.Get("offset")) + } + + searchRequest := &resource.SearchRequest{ + Tenant: tenant, + Kind: queryParams.Get("kind"), + QueryType: queryParams.Get("queryType"), + Query: queryParams.Get("query"), + Limit: int64(limit), + Offset: int64(offset), + } + res, err := b.unified.Search(r.Context(), searchRequest) if err != nil { - panic(err) + response.Error(500, "search request failed", err) } - if err := json.NewEncoder(w).Encode(res); err != nil { - panic(err) + + // TODO need a nicer way of handling this + // the [][]byte response already contains the marshalled JSON, so we don't need to re-encode it + rawMessages := make([]json.RawMessage, len(res.GetItems())) + for i, item := range res.GetItems() { + rawMessages[i] = item.Value } - }), + + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(rawMessages); err != nil { + response.Error(500, "failed to json encode raw response", err) + } + }, }, }, } diff --git a/pkg/services/accesscontrol/acimpl/accesscontrol.go b/pkg/services/accesscontrol/acimpl/accesscontrol.go index d540474abfc..ed3ee6eba45 100644 --- a/pkg/services/accesscontrol/acimpl/accesscontrol.go +++ b/pkg/services/accesscontrol/acimpl/accesscontrol.go @@ -3,12 +3,15 @@ package acimpl import ( "context" "errors" + "strconv" "time" openfgav1 "github.com/openfga/api/proto/openfga/v1" "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel" + "github.com/grafana/authlib/claims" + "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/metrics" @@ -127,6 +130,7 @@ func (a *AccessControl) evaluateZanzana(ctx context.Context, user identity.Reque a.log.Debug("evaluating zanzana", "user", tupleKey.User, "relation", tupleKey.Relation, "object", tupleKey.Object) allowed, err := a.Check(ctx, accesscontrol.CheckRequest{ + // Namespace: claims.OrgNamespaceFormatter(user.GetOrgID()), User: tupleKey.User, Relation: tupleKey.Relation, Object: tupleKey.Object, @@ -226,12 +230,44 @@ func (a *AccessControl) Check(ctx context.Context, req accesscontrol.CheckReques Relation: req.Relation, Object: req.Object, } - in := &openfgav1.CheckRequest{TupleKey: key} + + in := &openfgav1.CheckRequest{ + TupleKey: key, + } + + // Check direct access to resource first res, err := a.zclient.Check(ctx, in) if err != nil { return false, err } - return res.Allowed, err + + // no need to check folder access + if res.Allowed || req.Parent == "" { + return res.Allowed, nil + } + + // Check access through the parent folder + ns, err := claims.ParseNamespace(req.Namespace) + if err != nil { + return false, err + } + + folderKey := &openfgav1.CheckRequestTupleKey{ + User: req.User, + Relation: zanzana.TranslateToFolderRelation(req.Relation, req.ObjectType), + Object: zanzana.NewScopedTupleEntry(zanzana.TypeFolder, req.Parent, "", strconv.FormatInt(ns.OrgID, 10)), + } + + folderReq := &openfgav1.CheckRequest{ + TupleKey: folderKey, + } + + folderRes, err := a.zclient.Check(ctx, folderReq) + if err != nil { + return false, err + } + + return folderRes.Allowed, nil } func (a *AccessControl) ListObjects(ctx context.Context, req accesscontrol.ListObjectsRequest) ([]string, error) { diff --git a/pkg/services/accesscontrol/migrator/zanzana.go b/pkg/services/accesscontrol/migrator/zanzana.go index f27316f2630..7c27f31b624 100644 --- a/pkg/services/accesscontrol/migrator/zanzana.go +++ b/pkg/services/accesscontrol/migrator/zanzana.go @@ -39,7 +39,6 @@ func NewZanzanaSynchroniser(client zanzana.Client, store db.DB, collectors ...Tu teamMembershipCollector(store), managedPermissionsCollector(store), folderTreeCollector(store), - dashboardFolderCollector(store), basicRolesCollector(store), customRolesCollector(store), basicRoleAssignemtCollector(store), @@ -58,6 +57,7 @@ func NewZanzanaSynchroniser(client zanzana.Client, store db.DB, collectors ...Tu // Sync runs all collectors and tries to write all collected tuples. // It will skip over any "sync group" that has already been written. func (z *ZanzanaSynchroniser) Sync(ctx context.Context) error { + z.log.Info("Starting zanzana permissions sync") ctx, span := tracer.Start(ctx, "accesscontrol.migrator.Sync") defer span.End() @@ -246,47 +246,6 @@ func folderTreeCollector(store db.DB) TupleCollector { } } -// dashboardFolderCollector collects information about dashboards parent folders -func dashboardFolderCollector(store db.DB) TupleCollector { - return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error { - ctx, span := tracer.Start(ctx, "accesscontrol.migrator.dashboardFolderCollector") - defer span.End() - - const collectorID = "folder" - query := ` - SELECT org_id, uid, folder_uid, is_folder FROM dashboard - WHERE is_folder = ` + store.GetDialect().BooleanStr(false) + ` - AND folder_uid IS NOT NULL - ` - type dashboard struct { - OrgID int64 `xorm:"org_id"` - UID string `xorm:"uid"` - ParentUID string `xorm:"folder_uid"` - } - - var dashboards []dashboard - err := store.WithDbSession(ctx, func(sess *db.Session) error { - return sess.SQL(query).Find(&dashboards) - }) - - if err != nil { - return err - } - - for _, d := range dashboards { - tuple := &openfgav1.TupleKey{ - User: zanzana.NewScopedTupleEntry(zanzana.TypeFolder, d.ParentUID, "", strconv.FormatInt(d.OrgID, 10)), - Object: zanzana.NewScopedTupleEntry(zanzana.TypeDashboard, d.UID, "", strconv.FormatInt(d.OrgID, 10)), - Relation: zanzana.RelationParent, - } - - tuples[collectorID] = append(tuples[collectorID], tuple) - } - - return nil - } -} - // basicRolesCollector migrates basic roles to OpenFGA tuples func basicRolesCollector(store db.DB) TupleCollector { return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error { diff --git a/pkg/services/accesscontrol/models.go b/pkg/services/accesscontrol/models.go index c28a01429e1..9ee384540c1 100644 --- a/pkg/services/accesscontrol/models.go +++ b/pkg/services/accesscontrol/models.go @@ -589,9 +589,12 @@ type QueryWithOrg struct { } type CheckRequest struct { - User string - Relation string - Object string + Namespace string + User string + Relation string + Object string + ObjectType string + Parent string } type ListObjectsRequest struct { diff --git a/pkg/services/apiserver/options/extra.go b/pkg/services/apiserver/options/extra.go index aaf1052f3b0..715c319a62f 100644 --- a/pkg/services/apiserver/options/extra.go +++ b/pkg/services/apiserver/options/extra.go @@ -4,12 +4,15 @@ import ( "log/slog" "strconv" - "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/infra/log/slogadapter" "github.com/spf13/pflag" + genericfeatures "k8s.io/apiserver/pkg/features" genericapiserver "k8s.io/apiserver/pkg/server" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/component-base/logs" "k8s.io/klog/v2" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/log/slogadapter" ) type ExtraOptions struct { @@ -40,7 +43,11 @@ func (o *ExtraOptions) Validate() []error { func (o *ExtraOptions) ApplyTo(c *genericapiserver.RecommendedConfig) error { handler := slogadapter.New(log.New("grafana-apiserver")) logger := slog.New(handler) - + if err := utilfeature.DefaultMutableFeatureGate.SetFromMap(map[string]bool{ + string(genericfeatures.APIServerTracing): false, + }); err != nil { + return err + } klog.SetSlogLogger(logger) if _, err := logs.GlogSetter(strconv.Itoa(o.Verbosity)); err != nil { logger.Error("failed to set log level", "error", err) diff --git a/pkg/services/authn/authnimpl/service.go b/pkg/services/authn/authnimpl/service.go index e7182dcab52..644adebc386 100644 --- a/pkg/services/authn/authnimpl/service.go +++ b/pkg/services/authn/authnimpl/service.go @@ -491,7 +491,7 @@ func orgIDFromHeader(req *http.Request) int64 { } func (s *Service) resolveExternalSessionFromIdentity(ctx context.Context, identity *authn.Identity, userID int64) *auth.ExternalSession { - if identity.OAuthToken == nil { + if identity.OAuthToken == nil && identity.SAMLSession == nil { return nil } @@ -506,18 +506,26 @@ func (s *Service) resolveExternalSessionFromIdentity(ctx context.Context, identi UserAuthID: info.Id, UserID: userID, } - extSession.AccessToken = identity.OAuthToken.AccessToken - extSession.RefreshToken = identity.OAuthToken.RefreshToken - extSession.ExpiresAt = identity.OAuthToken.Expiry - if idToken, ok := identity.OAuthToken.Extra("id_token").(string); ok && idToken != "" { - extSession.IDToken = idToken + if identity.OAuthToken != nil { + extSession.AccessToken = identity.OAuthToken.AccessToken + extSession.RefreshToken = identity.OAuthToken.RefreshToken + extSession.ExpiresAt = identity.OAuthToken.Expiry + + if idToken, ok := identity.OAuthToken.Extra("id_token").(string); ok && idToken != "" { + extSession.IDToken = idToken + } + + // As of https://openid.net/specs/openid-connect-session-1_0.html + if sessionState, ok := identity.OAuthToken.Extra("session_state").(string); ok && sessionState != "" { + extSession.SessionID = sessionState + } + + return extSession } - // As of https://openid.net/specs/openid-connect-session-1_0.html - if sessionState, ok := identity.OAuthToken.Extra("session_state").(string); ok && sessionState != "" { - extSession.SessionID = sessionState - } + extSession.SessionID = identity.SAMLSession.SessionIndex + extSession.NameID = identity.SAMLSession.NameID return extSession } diff --git a/pkg/services/authn/authnimpl/sync/org_sync.go b/pkg/services/authn/authnimpl/sync/org_sync.go index 87d79215ce5..8a50d4cd73f 100644 --- a/pkg/services/authn/authnimpl/sync/org_sync.go +++ b/pkg/services/authn/authnimpl/sync/org_sync.go @@ -87,18 +87,25 @@ func (s *OrgSync) SyncOrgRolesHook(ctx context.Context, id *authn.Identity, _ *a orgIDs := make([]int64, 0, len(id.OrgRoles)) // add any new org roles for orgId, orgRole := range id.OrgRoles { - orgIDs = append(orgIDs, orgId) if _, exists := handledOrgIds[orgId]; exists { + orgIDs = append(orgIDs, orgId) continue } // add role cmd := &org.AddOrgUserCommand{UserID: userID, Role: orgRole, OrgID: orgId} err := s.orgService.AddOrgUser(ctx, cmd) - if err != nil && !errors.Is(err, org.ErrOrgNotFound) { + + if errors.Is(err, org.ErrOrgNotFound) { + continue + } + + if err != nil { ctxLogger.Error("Failed to update active org for user", "error", err) return err } + + orgIDs = append(orgIDs, orgId) } // delete any removed org roles diff --git a/pkg/services/authn/authnimpl/sync/org_sync_test.go b/pkg/services/authn/authnimpl/sync/org_sync_test.go index b62bcbcbf00..246aec07399 100644 --- a/pkg/services/authn/authnimpl/sync/org_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/org_sync_test.go @@ -24,7 +24,8 @@ import ( ) func TestOrgSync_SyncOrgRolesHook(t *testing.T) { - orgService := &orgtest.FakeOrgService{ExpectedUserOrgDTO: []*org.UserOrgDTO{ + orgService := &orgtest.MockService{} + orgService.On("GetUserOrgList", mock.Anything, mock.Anything).Return([]*org.UserOrgDTO{ { OrgID: 1, Role: org.RoleEditor, @@ -33,14 +34,16 @@ func TestOrgSync_SyncOrgRolesHook(t *testing.T) { OrgID: 3, Role: org.RoleViewer, }, - }, - ExpectedOrgListResponse: orgtest.OrgListResponse{ - { - OrgID: 3, - Response: nil, - }, - }, - } + }, nil) + orgService.On("RemoveOrgUser", mock.Anything, mock.MatchedBy(func(cmd *org.RemoveOrgUserCommand) bool { + return cmd.OrgID == 3 && cmd.UserID == 1 + })).Return(nil) + orgService.On("UpdateOrgUser", mock.Anything, mock.MatchedBy(func(cmd *org.UpdateOrgUserCommand) bool { + return cmd.OrgID == 1 && cmd.UserID == 1 && cmd.Role == org.RoleAdmin + })).Return(nil) + orgService.On("AddOrgUser", mock.Anything, mock.MatchedBy(func(cmd *org.AddOrgUserCommand) bool { + return cmd.OrgID == 2 && cmd.UserID == 1 && cmd.Role == org.RoleEditor + })).Return(org.ErrOrgNotFound) acService := &actest.FakeService{} userService := &usertest.FakeUserService{ExpectedUser: &user.User{ ID: 1, @@ -67,7 +70,7 @@ func TestOrgSync_SyncOrgRolesHook(t *testing.T) { wantID *authn.Identity }{ { - name: "add user to multiple orgs", + name: "add user to multiple orgs, should not set the user's default orgID to an org that does not exist", fields: fields{ userService: userService, orgService: orgService, @@ -100,7 +103,7 @@ func TestOrgSync_SyncOrgRolesHook(t *testing.T) { Name: "test", Email: "test", OrgRoles: map[int64]identity.RoleType{1: org.RoleAdmin, 2: org.RoleEditor}, - OrgID: 1, //set using org + OrgID: 1, // set using org IsGrafanaAdmin: ptrBool(false), ClientParams: authn.ClientParams{ SyncOrgRoles: true, diff --git a/pkg/services/authn/clients/ext_jwt.go b/pkg/services/authn/clients/ext_jwt.go index 26f98338ec2..638101615a2 100644 --- a/pkg/services/authn/clients/ext_jwt.go +++ b/pkg/services/authn/clients/ext_jwt.go @@ -185,10 +185,10 @@ func (s *ExtendedJWT) authenticateAsService(accessTokenClaims authlib.Claims[aut return &authn.Identity{ ID: id, UID: id, + Name: id, Type: t, OrgID: s.cfg.DefaultOrgID(), AccessTokenClaims: &accessTokenClaims, - IDTokenClaims: nil, AuthenticatedBy: login.ExtendedJWTModule, AuthID: accessTokenClaims.Subject, AllowedKubernetesNamespace: accessTokenClaims.Rest.Namespace, diff --git a/pkg/services/authn/clients/ext_jwt_test.go b/pkg/services/authn/clients/ext_jwt_test.go index 6b61748dd03..3f9e9ee6197 100644 --- a/pkg/services/authn/clients/ext_jwt_test.go +++ b/pkg/services/authn/clients/ext_jwt_test.go @@ -228,6 +228,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) { want: &authn.Identity{ ID: "this-uid", UID: "this-uid", + Name: "this-uid", Type: claims.TypeAccessPolicy, OrgID: 1, AccessTokenClaims: &validAccessTokenClaims, @@ -246,6 +247,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) { want: &authn.Identity{ ID: "this-uid", UID: "this-uid", + Name: "this-uid", Type: claims.TypeAccessPolicy, OrgID: 1, AccessTokenClaims: &validAccessTokenClaimsWildcard, @@ -343,6 +345,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) { want: &authn.Identity{ ID: "this-uid", UID: "this-uid", + Name: "this-uid", Type: claims.TypeAccessPolicy, OrgID: 1, AccessTokenClaims: &validAccessTokenClaimsWithStackSet, @@ -369,6 +372,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) { want: &authn.Identity{ ID: "this-uid", UID: "this-uid", + Name: "this-uid", Type: claims.TypeAccessPolicy, OrgID: 1, AccessTokenClaims: &validAccessTokenClaimsWithDeprecatedStackClaimSet, diff --git a/pkg/services/authn/identity.go b/pkg/services/authn/identity.go index e137809c1d2..a072ee7d807 100644 --- a/pkg/services/authn/identity.go +++ b/pkg/services/authn/identity.go @@ -64,6 +64,8 @@ type Identity struct { Groups []string // OAuthToken is the OAuth token used to authenticate the entity. OAuthToken *oauth2.Token + // SAMLSession is the SAML session information. + SAMLSession *login.SAMLSession // SessionToken is the session token used to authenticate the entity. SessionToken *usertoken.UserToken // ClientParams are hints for the auth service on how to handle the identity. diff --git a/pkg/services/authz/zanzana/logger/logger.go b/pkg/services/authz/zanzana/logger/logger.go index ac953ed4f15..1e1f2a6435d 100644 --- a/pkg/services/authz/zanzana/logger/logger.go +++ b/pkg/services/authz/zanzana/logger/logger.go @@ -4,10 +4,14 @@ import ( "context" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/grafana/grafana/pkg/infra/log" + "github.com/openfga/openfga/pkg/logger" ) +var _ logger.Logger = (*ZanzanaLogger)(nil) + // ZanzanaLogger is a grafana logger wrapper compatible with OpenFGA logger interface type ZanzanaLogger struct { logger log.Logger @@ -36,6 +40,13 @@ func zapFieldsToArgs(fields []zap.Field) []any { return args } +// With implements logger.Logger. +func (l *ZanzanaLogger) With(fields ...zapcore.Field) logger.Logger { + return &ZanzanaLogger{ + logger: l.logger.New(zapFieldsToArgs(fields)...), + } +} + func (l *ZanzanaLogger) Debug(msg string, fields ...zap.Field) { l.logger.Debug(msg, zapFieldsToArgs(fields)...) } diff --git a/pkg/services/authz/zanzana/schema/dashboard.fga b/pkg/services/authz/zanzana/schema/dashboard.fga index ee08372ac7f..b9e77da81a1 100644 --- a/pkg/services/authz/zanzana/schema/dashboard.fga +++ b/pkg/services/authz/zanzana/schema/dashboard.fga @@ -28,18 +28,17 @@ extend type org type dashboard relations define org: [org] - define parent: [folder] - define read: [user, team#member, role#assignee] or dashboard_read from parent or dashboard_read from org - define write: [user, team#member, role#assignee] or dashboard_write from parent or dashboard_write from org - define delete: [user, team#member, role#assignee] or dashboard_delete from parent or dashboard_delete from org - define create: [user, team#member, role#assignee] or dashboard_create from parent or dashboard_create from org - define permissions_read: [user, team#member, role#assignee] or dashboard_permissions_read from parent or dashboard_permissions_read from org - define permissions_write: [user, team#member, role#assignee] or dashboard_permissions_write from parent or dashboard_permissions_write from org + define read: [user, team#member, role#assignee] or dashboard_read from org + define write: [user, team#member, role#assignee] or dashboard_write from org + define delete: [user, team#member, role#assignee] or dashboard_delete from org + define create: [user, team#member, role#assignee] or dashboard_create from org + define permissions_read: [user, team#member, role#assignee] or dashboard_permissions_read from org + define permissions_write: [user, team#member, role#assignee] or dashboard_permissions_write from org - define public_write: [user, team#member, role#assignee] or dashboard_public_write from parent or dashboard_public_write from org or write - define annotations_create: [user, team#member, role#assignee] or dashboard_annotations_create from parent or dashboard_annotations_create from org - define annotations_read: [user, team#member, role#assignee] or dashboard_annotations_read from parent or dashboard_annotations_read from org - define annotations_write: [user, team#member, role#assignee] or dashboard_annotations_write from parent or dashboard_annotations_write from org - define annotations_delete: [user, team#member, role#assignee] or dashboard_annotations_delete from parent or dashboard_annotations_delete from org + define public_write: [user, team#member, role#assignee] or dashboard_public_write from org or write + define annotations_create: [user, team#member, role#assignee] or dashboard_annotations_create from org + define annotations_read: [user, team#member, role#assignee] or dashboard_annotations_read from org + define annotations_write: [user, team#member, role#assignee] or dashboard_annotations_write from org + define annotations_delete: [user, team#member, role#assignee] or dashboard_annotations_delete from org diff --git a/pkg/services/authz/zanzana/store/assets/assets.go b/pkg/services/authz/zanzana/store/assets/assets.go deleted file mode 100644 index fb55d16eef2..00000000000 --- a/pkg/services/authz/zanzana/store/assets/assets.go +++ /dev/null @@ -1,10 +0,0 @@ -package assets - -import "embed" - -// EmbedMigrations within the grafana binary. -// -//go:embed migrations/* -var EmbedMigrations embed.FS - -const SQLiteMigrationDir = "migrations/sqlite" diff --git a/pkg/services/authz/zanzana/store/assets/migrations/sqlite/001_initialize_schema.sql b/pkg/services/authz/zanzana/store/assets/migrations/sqlite/001_initialize_schema.sql deleted file mode 100644 index ecceebfb58b..00000000000 --- a/pkg/services/authz/zanzana/store/assets/migrations/sqlite/001_initialize_schema.sql +++ /dev/null @@ -1,56 +0,0 @@ --- +goose Up -CREATE TABLE tuple ( - store CHAR(26) NOT NULL, - object_type VARCHAR(128) NOT NULL, - object_id VARCHAR(128) NOT NULL, - relation VARCHAR(50) NOT NULL, - _user VARCHAR(256) NOT NULL, - user_type VARCHAR(7) NOT NULL, - ulid CHAR(26) NOT NULL, - inserted_at TIMESTAMP NOT NULL, - PRIMARY KEY (store, object_type, object_id, relation, _user) -); - -CREATE UNIQUE INDEX idx_tuple_ulid ON tuple (ulid); - -CREATE TABLE authorization_model ( - store CHAR(26) NOT NULL, - authorization_model_id CHAR(26) NOT NULL, - type VARCHAR(256) NOT NULL, - type_definition BLOB, - PRIMARY KEY (store, authorization_model_id, type) -); - -CREATE TABLE store ( - id CHAR(26) PRIMARY KEY, - name VARCHAR(64) NOT NULL, - created_at TIMESTAMP NOT NULL, - updated_at TIMESTAMP, - deleted_at TIMESTAMP -); - -CREATE TABLE assertion ( - store CHAR(26) NOT NULL, - authorization_model_id CHAR(26) NOT NULL, - assertions BLOB, - PRIMARY KEY (store, authorization_model_id) -); - -CREATE TABLE changelog ( - store CHAR(26) NOT NULL, - object_type VARCHAR(256) NOT NULL, - object_id VARCHAR(256) NOT NULL, - relation VARCHAR(50) NOT NULL, - _user VARCHAR(512) NOT NULL, - operation INTEGER NOT NULL, - ulid CHAR(26) NOT NULL, - inserted_at TIMESTAMP NOT NULL, - PRIMARY KEY (store, ulid, object_type) -); - --- +goose Down -DROP TABLE tuple; -DROP TABLE authorization_model; -DROP TABLE store; -DROP TABLE assertion; -DROP TABLE changelog; diff --git a/pkg/services/authz/zanzana/store/assets/migrations/sqlite/002_add_authorization_model_version.sql b/pkg/services/authz/zanzana/store/assets/migrations/sqlite/002_add_authorization_model_version.sql deleted file mode 100644 index 26ce15ac18b..00000000000 --- a/pkg/services/authz/zanzana/store/assets/migrations/sqlite/002_add_authorization_model_version.sql +++ /dev/null @@ -1,5 +0,0 @@ --- +goose Up -ALTER TABLE authorization_model ADD COLUMN schema_version VARCHAR(5) NOT NULL DEFAULT '1.0'; - --- +goose Down -ALTER TABLE authorization_model DROP COLUMN schema_version; diff --git a/pkg/services/authz/zanzana/store/assets/migrations/sqlite/003_add_reverse_lookup_index.sql b/pkg/services/authz/zanzana/store/assets/migrations/sqlite/003_add_reverse_lookup_index.sql deleted file mode 100644 index 9a3ee2a82bc..00000000000 --- a/pkg/services/authz/zanzana/store/assets/migrations/sqlite/003_add_reverse_lookup_index.sql +++ /dev/null @@ -1,5 +0,0 @@ --- +goose Up -CREATE INDEX idx_reverse_lookup_user on tuple (store, object_type, relation, _user); - --- +goose Down -DROP INDEX idx_reverse_lookup_user on tuple; diff --git a/pkg/services/authz/zanzana/store/assets/migrations/sqlite/004_add_authorization_model_serialized_protobuf.sql b/pkg/services/authz/zanzana/store/assets/migrations/sqlite/004_add_authorization_model_serialized_protobuf.sql deleted file mode 100644 index 1e47f290e51..00000000000 --- a/pkg/services/authz/zanzana/store/assets/migrations/sqlite/004_add_authorization_model_serialized_protobuf.sql +++ /dev/null @@ -1,5 +0,0 @@ --- +goose Up -ALTER TABLE authorization_model ADD COLUMN serialized_protobuf LONGBLOB; - --- +goose Down -ALTER TABLE authorization_model DROP COLUMN serialized_protobuf; diff --git a/pkg/services/authz/zanzana/store/assets/migrations/sqlite/005_add_conditions_to_tuples.sql b/pkg/services/authz/zanzana/store/assets/migrations/sqlite/005_add_conditions_to_tuples.sql deleted file mode 100644 index 37d039ca799..00000000000 --- a/pkg/services/authz/zanzana/store/assets/migrations/sqlite/005_add_conditions_to_tuples.sql +++ /dev/null @@ -1,11 +0,0 @@ --- +goose Up -ALTER TABLE tuple ADD COLUMN condition_name VARCHAR(256); -ALTER TABLE tuple ADD COLUMN condition_context LONGBLOB; -ALTER TABLE changelog ADD COLUMN condition_name VARCHAR(256); -ALTER TABLE changelog ADD COLUMN condition_context LONGBLOB; - --- +goose Down -ALTER TABLE tuple DROP COLUMN condition_name; -ALTER TABLE tuple DROP COLUMN condition_context; -ALTER TABLE changelog DROP COLUMN condition_name; -ALTER TABLE changelog DROP COLUMN condition_context; diff --git a/pkg/services/authz/zanzana/store/migration/migrator.go b/pkg/services/authz/zanzana/store/migration/migrator.go index 3f0d13f7343..3a2db5aa8c9 100644 --- a/pkg/services/authz/zanzana/store/migration/migrator.go +++ b/pkg/services/authz/zanzana/store/migration/migrator.go @@ -15,13 +15,17 @@ import ( func Run(cfg *setting.Cfg, typ, connStr string, fs embed.FS, path string) error { engine, err := xorm.NewEngine(typ, connStr) if err != nil { - return fmt.Errorf("failed to parse database config: %w", err) + return fmt.Errorf("failed to create db engine: %w", err) } m := migrator.NewMigrator(engine, cfg) m.AddCreateMigration() - return RunWithMigrator(m, cfg, fs, path) + if err := RunWithMigrator(m, cfg, fs, path); err != nil { + return err + } + + return engine.Close() } func RunWithMigrator(m *migrator.Migrator, cfg *setting.Cfg, fs embed.FS, path string) error { diff --git a/pkg/services/authz/zanzana/store/sqlite/config.go b/pkg/services/authz/zanzana/store/sqlite/config.go deleted file mode 100644 index b47404d6028..00000000000 --- a/pkg/services/authz/zanzana/store/sqlite/config.go +++ /dev/null @@ -1,15 +0,0 @@ -package sqlite - -import "github.com/openfga/openfga/pkg/storage/sqlcommon" - -type Config struct { - *sqlcommon.Config - QueryRetries int -} - -func NewConfig() *Config { - return &Config{ - Config: sqlcommon.NewConfig(), - QueryRetries: 0, - } -} diff --git a/pkg/services/authz/zanzana/store/sqlite/store.go b/pkg/services/authz/zanzana/store/sqlite/store.go deleted file mode 100644 index 0860eb3b968..00000000000 --- a/pkg/services/authz/zanzana/store/sqlite/store.go +++ /dev/null @@ -1,821 +0,0 @@ -package sqlite - -import ( - "context" - "database/sql" - "encoding/json" - "errors" - "fmt" - "net/url" - "strings" - "time" - - sq "github.com/Masterminds/squirrel" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/collectors" - "go.opentelemetry.io/otel" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/structpb" - "google.golang.org/protobuf/types/known/timestamppb" - - // Pull in sqlite driver. - "github.com/mattn/go-sqlite3" - - openfgav1 "github.com/openfga/api/proto/openfga/v1" - "github.com/openfga/openfga/pkg/logger" - "github.com/openfga/openfga/pkg/storage" - "github.com/openfga/openfga/pkg/storage/sqlcommon" - tupleUtils "github.com/openfga/openfga/pkg/tuple" -) - -var tracer = otel.Tracer("openfga/pkg/storage/sqlite") - -// SQLite provides a SQLite based implementation of [storage.OpenFGADatastore]. -type SQLite struct { - stbl sq.StatementBuilderType - cfg *Config - db *sql.DB - dbInfo *sqlcommon.DBInfo - sqlTime sq.Sqlizer - logger logger.Logger - dbStatsCollector prometheus.Collector -} - -// Ensures that SQLite implements the OpenFGADatastore interface. -var _ storage.OpenFGADatastore = (*SQLite)(nil) - -// New creates a new [SQLite] storage. -func New(uri string, cfg *Config) (*SQLite, error) { - // Set journal mode and busy timeout pragmas if not specified. - query := url.Values{} - var err error - - if i := strings.Index(uri, "?"); i != -1 { - query, err = url.ParseQuery(uri[i+1:]) - if err != nil { - return nil, fmt.Errorf("error parsing dsn: %w", err) - } - - uri = uri[:i] - } - - foundJournalMode := false - foundBusyTimeout := false - for _, val := range query["_pragma"] { - if strings.HasPrefix(val, "journal_mode") { - foundJournalMode = true - } else if strings.HasPrefix(val, "busy_timeout") { - foundBusyTimeout = true - } - } - - if !foundJournalMode { - query.Add("_pragma", "journal_mode(WAL)") - } - if !foundBusyTimeout { - query.Add("_pragma", "busy_timeout(500)") - } - - uri += "?" + query.Encode() - - db, err := sql.Open("sqlite", uri) - if err != nil { - return nil, fmt.Errorf("initialize sqlite connection: %w", err) - } - - return NewWithDB(db, cfg) -} - -// NewWithDB creates a new [SQLite] storage using provided [*sql.DB] -func NewWithDB(db *sql.DB, cfg *Config) (*SQLite, error) { - var collector prometheus.Collector - if cfg.ExportMetrics { - collector = collectors.NewDBStatsCollector(db, "openfga") - if err := prometheus.Register(collector); err != nil { - return nil, fmt.Errorf("initialize metrics: %w", err) - } - } - - sqlTime := sq.Expr("datetime('subsec')") - stbl := sq.StatementBuilder.RunWith(db) - dbInfo := sqlcommon.NewDBInfo(db, stbl, sqlTime) - - return &SQLite{ - cfg: cfg, - stbl: stbl, - db: db, - sqlTime: sqlTime, - dbInfo: dbInfo, - logger: cfg.Logger, - dbStatsCollector: collector, - }, nil -} - -// Close see [storage.OpenFGADatastore].Close. -func (m *SQLite) Close() { - if m.dbStatsCollector != nil { - prometheus.Unregister(m.dbStatsCollector) - } - _ = m.db.Close() -} - -// Read see [storage.RelationshipTupleReader].Read. -func (m *SQLite) Read(ctx context.Context, store string, tupleKey *openfgav1.TupleKey) (storage.TupleIterator, error) { - ctx, span := tracer.Start(ctx, "sqlite.Read") - defer span.End() - - return m.read(ctx, store, tupleKey, nil) -} - -// ReadPage see [storage.RelationshipTupleReader].ReadPage. -func (m *SQLite) ReadPage( - ctx context.Context, - store string, - tupleKey *openfgav1.TupleKey, - opts storage.PaginationOptions, -) ([]*openfgav1.Tuple, []byte, error) { - ctx, span := tracer.Start(ctx, "sqlite.ReadPage") - defer span.End() - - iter, err := m.read(ctx, store, tupleKey, &opts) - if err != nil { - return nil, nil, err - } - defer iter.Stop() - - return iter.ToArray(opts) -} - -func (m *SQLite) read(ctx context.Context, store string, tupleKey *openfgav1.TupleKey, opts *storage.PaginationOptions) (*sqlcommon.SQLTupleIterator, error) { - ctx, span := tracer.Start(ctx, "sqlite.read") - defer span.End() - - sb := m.stbl. - Select( - "store", "object_type", "object_id", "relation", "_user", - "condition_name", "condition_context", "ulid", "inserted_at", - ). - From("tuple"). - Where(sq.Eq{"store": store}) - if opts != nil { - sb = sb.OrderBy("ulid") - } - objectType, objectID := tupleUtils.SplitObject(tupleKey.GetObject()) - if objectType != "" { - sb = sb.Where(sq.Eq{"object_type": objectType}) - } - if objectID != "" { - sb = sb.Where(sq.Eq{"object_id": objectID}) - } - if tupleKey.GetRelation() != "" { - sb = sb.Where(sq.Eq{"relation": tupleKey.GetRelation()}) - } - if tupleKey.GetUser() != "" { - sb = sb.Where(sq.Eq{"_user": tupleKey.GetUser()}) - } - if opts != nil && opts.From != "" { - token, err := sqlcommon.UnmarshallContToken(opts.From) - if err != nil { - return nil, err - } - sb = sb.Where(sq.GtOrEq{"ulid": token.Ulid}) - } - if opts != nil && opts.PageSize != 0 { - sb = sb.Limit(uint64(opts.PageSize + 1)) // + 1 is used to determine whether to return a continuation token. - } - - rows, err := sb.QueryContext(ctx) - if err != nil { - return nil, handleSQLError(err) - } - - return sqlcommon.NewSQLTupleIterator(rows), nil -} - -// Write see [storage.RelationshipTupleWriter].Write. -func (m *SQLite) Write(ctx context.Context, store string, deletes storage.Deletes, writes storage.Writes) error { - ctx, span := tracer.Start(ctx, "sqlite.Write") - defer span.End() - - if len(deletes)+len(writes) > m.MaxTuplesPerWrite() { - return storage.ErrExceededWriteBatchLimit - } - - return m.busyRetry(func() error { - now := time.Now().UTC() - return write(ctx, m.db, m.stbl, m.sqlTime, store, deletes, writes, now) - }) -} - -// ReadUserTuple see [storage.RelationshipTupleReader].ReadUserTuple. -func (m *SQLite) ReadUserTuple(ctx context.Context, store string, tupleKey *openfgav1.TupleKey) (*openfgav1.Tuple, error) { - ctx, span := tracer.Start(ctx, "sqlite.ReadUserTuple") - defer span.End() - - objectType, objectID := tupleUtils.SplitObject(tupleKey.GetObject()) - userType := tupleUtils.GetUserTypeFromUser(tupleKey.GetUser()) - - var conditionName sql.NullString - var conditionContext []byte - var record storage.TupleRecord - err := m.stbl. - Select( - "object_type", "object_id", "relation", "_user", - "condition_name", "condition_context", - ). - From("tuple"). - Where(sq.Eq{ - "store": store, - "object_type": objectType, - "object_id": objectID, - "relation": tupleKey.GetRelation(), - "_user": tupleKey.GetUser(), - "user_type": userType, - }). - QueryRowContext(ctx). - Scan( - &record.ObjectType, - &record.ObjectID, - &record.Relation, - &record.User, - &conditionName, - &conditionContext, - ) - if err != nil { - return nil, handleSQLError(err) - } - - if conditionName.String != "" { - record.ConditionName = conditionName.String - - if conditionContext != nil { - var conditionContextStruct structpb.Struct - if err := proto.Unmarshal(conditionContext, &conditionContextStruct); err != nil { - return nil, err - } - record.ConditionContext = &conditionContextStruct - } - } - - return record.AsTuple(), nil -} - -// ReadUsersetTuples see [storage.RelationshipTupleReader].ReadUsersetTuples. -func (m *SQLite) ReadUsersetTuples( - ctx context.Context, - store string, - filter storage.ReadUsersetTuplesFilter, -) (storage.TupleIterator, error) { - ctx, span := tracer.Start(ctx, "sqlite.ReadUsersetTuples") - defer span.End() - - sb := m.stbl. - Select( - "store", "object_type", "object_id", "relation", "_user", - "condition_name", "condition_context", "ulid", "inserted_at", - ). - From("tuple"). - Where(sq.Eq{"store": store}). - Where(sq.Eq{"user_type": tupleUtils.UserSet}) - - objectType, objectID := tupleUtils.SplitObject(filter.Object) - if objectType != "" { - sb = sb.Where(sq.Eq{"object_type": objectType}) - } - if objectID != "" { - sb = sb.Where(sq.Eq{"object_id": objectID}) - } - if filter.Relation != "" { - sb = sb.Where(sq.Eq{"relation": filter.Relation}) - } - if len(filter.AllowedUserTypeRestrictions) > 0 { - orConditions := sq.Or{} - for _, userset := range filter.AllowedUserTypeRestrictions { - if _, ok := userset.GetRelationOrWildcard().(*openfgav1.RelationReference_Relation); ok { - orConditions = append(orConditions, sq.Like{"_user": userset.GetType() + ":%#" + userset.GetRelation()}) - } - if _, ok := userset.GetRelationOrWildcard().(*openfgav1.RelationReference_Wildcard); ok { - orConditions = append(orConditions, sq.Eq{"_user": userset.GetType() + ":*"}) - } - } - sb = sb.Where(orConditions) - } - rows, err := sb.QueryContext(ctx) - if err != nil { - return nil, handleSQLError(err) - } - - return sqlcommon.NewSQLTupleIterator(rows), nil -} - -// ReadStartingWithUser see [storage.RelationshipTupleReader].ReadStartingWithUser. -func (m *SQLite) ReadStartingWithUser( - ctx context.Context, - store string, - opts storage.ReadStartingWithUserFilter, -) (storage.TupleIterator, error) { - ctx, span := tracer.Start(ctx, "sqlite.ReadStartingWithUser") - defer span.End() - - targetUsersArg := make([]string, 0, len(opts.UserFilter)) - for _, u := range opts.UserFilter { - targetUser := u.GetObject() - if u.GetRelation() != "" { - targetUser = strings.Join([]string{u.GetObject(), u.GetRelation()}, "#") - } - targetUsersArg = append(targetUsersArg, targetUser) - } - - rows, err := m.stbl. - Select( - "store", "object_type", "object_id", "relation", "_user", - "condition_name", "condition_context", "ulid", "inserted_at", - ). - From("tuple"). - Where(sq.Eq{ - "store": store, - "object_type": opts.ObjectType, - "relation": opts.Relation, - "_user": targetUsersArg, - }).QueryContext(ctx) - if err != nil { - return nil, handleSQLError(err) - } - - return sqlcommon.NewSQLTupleIterator(rows), nil -} - -// MaxTuplesPerWrite see [storage.RelationshipTupleWriter].MaxTuplesPerWrite. -func (m *SQLite) MaxTuplesPerWrite() int { - return m.cfg.MaxTuplesPerWriteField -} - -// ReadAuthorizationModel see [storage.AuthorizationModelReadBackend].ReadAuthorizationModel. -func (m *SQLite) ReadAuthorizationModel(ctx context.Context, store string, modelID string) (*openfgav1.AuthorizationModel, error) { - ctx, span := tracer.Start(ctx, "sqlite.ReadAuthorizationModel") - defer span.End() - - return sqlcommon.ReadAuthorizationModel(ctx, m.dbInfo, store, modelID) -} - -// ReadAuthorizationModels see [storage.AuthorizationModelReadBackend].ReadAuthorizationModels. -func (m *SQLite) ReadAuthorizationModels( - ctx context.Context, - store string, - opts storage.PaginationOptions, -) ([]*openfgav1.AuthorizationModel, []byte, error) { - ctx, span := tracer.Start(ctx, "sqlite.ReadAuthorizationModels") - defer span.End() - - sb := m.stbl.Select("authorization_model_id"). - Distinct(). - From("authorization_model"). - Where(sq.Eq{"store": store}). - OrderBy("authorization_model_id desc") - - if opts.From != "" { - token, err := sqlcommon.UnmarshallContToken(opts.From) - if err != nil { - return nil, nil, err - } - sb = sb.Where(sq.LtOrEq{"authorization_model_id": token.Ulid}) - } - if opts.PageSize > 0 { - sb = sb.Limit(uint64(opts.PageSize + 1)) // + 1 is used to determine whether to return a continuation token. - } - - rows, err := sb.QueryContext(ctx) - if err != nil { - return nil, nil, handleSQLError(err) - } - defer func() { _ = rows.Close() }() - - var modelIDs []string - var modelID string - - for rows.Next() { - err = rows.Scan(&modelID) - if err != nil { - return nil, nil, handleSQLError(err) - } - - modelIDs = append(modelIDs, modelID) - } - - if err := rows.Err(); err != nil { - return nil, nil, handleSQLError(err) - } - - var token []byte - numModelIDs := len(modelIDs) - if len(modelIDs) > opts.PageSize { - numModelIDs = opts.PageSize - token, err = json.Marshal(sqlcommon.NewContToken(modelID, "")) - if err != nil { - return nil, nil, err - } - } - - // TODO: make this concurrent with a maximum of 5 goroutines. This may be helpful: - // https://stackoverflow.com/questions/25306073/always-have-x-number-of-goroutines-running-at-any-time - models := make([]*openfgav1.AuthorizationModel, 0, numModelIDs) - // We use numModelIDs here to avoid retrieving possibly one extra model. - for i := 0; i < numModelIDs; i++ { - model, err := m.ReadAuthorizationModel(ctx, store, modelIDs[i]) - if err != nil { - return nil, nil, err - } - models = append(models, model) - } - - return models, token, nil -} - -// FindLatestAuthorizationModel see [storage.AuthorizationModelReadBackend].FindLatestAuthorizationModel. -func (m *SQLite) FindLatestAuthorizationModel(ctx context.Context, store string) (*openfgav1.AuthorizationModel, error) { - ctx, span := tracer.Start(ctx, "sqlite.FindLatestAuthorizationModel") - defer span.End() - - return sqlcommon.FindLatestAuthorizationModel(ctx, m.dbInfo, store) -} - -// MaxTypesPerAuthorizationModel see [storage.TypeDefinitionWriteBackend].MaxTypesPerAuthorizationModel. -func (m *SQLite) MaxTypesPerAuthorizationModel() int { - return m.cfg.MaxTypesPerModelField -} - -// WriteAuthorizationModel see [storage.TypeDefinitionWriteBackend].WriteAuthorizationModel. -func (m *SQLite) WriteAuthorizationModel(ctx context.Context, store string, model *openfgav1.AuthorizationModel) error { - ctx, span := tracer.Start(ctx, "sqlite.WriteAuthorizationModel") - defer span.End() - - typeDefinitions := model.GetTypeDefinitions() - - if len(typeDefinitions) > m.MaxTypesPerAuthorizationModel() { - return storage.ExceededMaxTypeDefinitionsLimitError(m.MaxTypesPerAuthorizationModel()) - } - - return m.busyRetry(func() error { - return sqlcommon.WriteAuthorizationModel(ctx, m.dbInfo, store, model) - }) -} - -// CreateStore adds a new store to the SQLite storage. -func (m *SQLite) CreateStore(ctx context.Context, store *openfgav1.Store) (*openfgav1.Store, error) { - ctx, span := tracer.Start(ctx, "sqlite.CreateStore") - defer span.End() - - txn, err := m.db.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return nil, handleSQLError(err) - } - defer func() { - _ = txn.Rollback() - }() - - _, err = m.stbl. - Insert("store"). - Columns("id", "name", "created_at", "updated_at"). - Values(store.GetId(), store.GetName(), sq.Expr("datetime('subsec')"), sq.Expr("datetime('subsec')")). - RunWith(txn). - ExecContext(ctx) - if err != nil { - return nil, handleSQLError(err) - } - - var createdAt time.Time - var id, name string - err = m.stbl. - Select("id", "name", "created_at"). - From("store"). - Where(sq.Eq{"id": store.GetId()}). - RunWith(txn). - QueryRowContext(ctx). - Scan(&id, &name, &createdAt) - if err != nil { - return nil, handleSQLError(err) - } - - err = txn.Commit() - if err != nil { - return nil, handleSQLError(err) - } - - return &openfgav1.Store{ - Id: id, - Name: name, - CreatedAt: timestamppb.New(createdAt), - UpdatedAt: timestamppb.New(createdAt), - }, nil -} - -// GetStore retrieves the details of a specific store from the SQLite using its storeID. -func (m *SQLite) GetStore(ctx context.Context, id string) (*openfgav1.Store, error) { - ctx, span := tracer.Start(ctx, "sqlite.GetStore") - defer span.End() - - row := m.stbl. - Select("id", "name", "created_at", "updated_at"). - From("store"). - Where(sq.Eq{ - "id": id, - "deleted_at": nil, - }). - QueryRowContext(ctx) - - var storeID, name string - var createdAt, updatedAt time.Time - err := row.Scan(&storeID, &name, &createdAt, &updatedAt) - if err != nil { - if errors.Is(err, sql.ErrNoRows) { - return nil, storage.ErrNotFound - } - return nil, handleSQLError(err) - } - - return &openfgav1.Store{ - Id: storeID, - Name: name, - CreatedAt: timestamppb.New(createdAt), - UpdatedAt: timestamppb.New(updatedAt), - }, nil -} - -// ListStores provides a paginated list of all stores present in the SQLite storage. -func (m *SQLite) ListStores(ctx context.Context, opts storage.PaginationOptions) ([]*openfgav1.Store, []byte, error) { - ctx, span := tracer.Start(ctx, "sqlite.ListStores") - defer span.End() - - sb := m.stbl. - Select("id", "name", "created_at", "updated_at"). - From("store"). - Where(sq.Eq{"deleted_at": nil}). - OrderBy("id") - - if opts.From != "" { - token, err := sqlcommon.UnmarshallContToken(opts.From) - if err != nil { - return nil, nil, err - } - sb = sb.Where(sq.GtOrEq{"id": token.Ulid}) - } - if opts.PageSize > 0 { - sb = sb.Limit(uint64(opts.PageSize + 1)) // + 1 is used to determine whether to return a continuation token. - } - - rows, err := sb.QueryContext(ctx) - if err != nil { - return nil, nil, handleSQLError(err) - } - defer func() { _ = rows.Close() }() - - var stores []*openfgav1.Store - var id string - for rows.Next() { - var name string - var createdAt, updatedAt time.Time - err := rows.Scan(&id, &name, &createdAt, &updatedAt) - if err != nil { - return nil, nil, handleSQLError(err) - } - - stores = append(stores, &openfgav1.Store{ - Id: id, - Name: name, - CreatedAt: timestamppb.New(createdAt), - UpdatedAt: timestamppb.New(updatedAt), - }) - } - - if err := rows.Err(); err != nil { - return nil, nil, handleSQLError(err) - } - - if len(stores) > opts.PageSize { - contToken, err := json.Marshal(sqlcommon.NewContToken(id, "")) - if err != nil { - return nil, nil, err - } - return stores[:opts.PageSize], contToken, nil - } - - return stores, nil, nil -} - -// DeleteStore removes a store from the SQLite storage. -func (m *SQLite) DeleteStore(ctx context.Context, id string) error { - ctx, span := tracer.Start(ctx, "sqlite.DeleteStore") - defer span.End() - - _, err := m.stbl. - Update("store"). - Set("deleted_at", sq.Expr("datetime('subsec')")). - Where(sq.Eq{"id": id}). - ExecContext(ctx) - if err != nil { - return handleSQLError(err) - } - - return nil -} - -// WriteAssertions see [storage.AssertionsBackend].WriteAssertions. -func (m *SQLite) WriteAssertions(ctx context.Context, store, modelID string, assertions []*openfgav1.Assertion) error { - ctx, span := tracer.Start(ctx, "sqlite.WriteAssertions") - defer span.End() - - marshalledAssertions, err := proto.Marshal(&openfgav1.Assertions{Assertions: assertions}) - if err != nil { - return err - } - - return m.busyRetry(func() error { - _, err = m.stbl. - Insert("assertion"). - Columns("store", "authorization_model_id", "assertions"). - Values(store, modelID, marshalledAssertions). - Suffix("ON CONFLICT(store,authorization_model_id) DO UPDATE SET assertions = ?", marshalledAssertions). - ExecContext(ctx) - if err != nil { - return handleSQLError(err) - } - - return nil - }) -} - -// ReadAssertions see [storage.AssertionsBackend].ReadAssertions. -func (m *SQLite) ReadAssertions(ctx context.Context, store, modelID string) ([]*openfgav1.Assertion, error) { - ctx, span := tracer.Start(ctx, "sqlite.ReadAssertions") - defer span.End() - - var marshalledAssertions []byte - err := m.stbl. - Select("assertions"). - From("assertion"). - Where(sq.Eq{ - "store": store, - "authorization_model_id": modelID, - }). - QueryRowContext(ctx). - Scan(&marshalledAssertions) - if err != nil { - if errors.Is(err, sql.ErrNoRows) { - return []*openfgav1.Assertion{}, nil - } - return nil, handleSQLError(err) - } - - var assertions openfgav1.Assertions - err = proto.Unmarshal(marshalledAssertions, &assertions) - if err != nil { - return nil, err - } - - return assertions.GetAssertions(), nil -} - -// ReadChanges see [storage.ChangelogBackend].ReadChanges. -func (m *SQLite) ReadChanges( - ctx context.Context, - store, objectTypeFilter string, - opts storage.PaginationOptions, - horizonOffset time.Duration, -) ([]*openfgav1.TupleChange, []byte, error) { - ctx, span := tracer.Start(ctx, "sqlite.ReadChanges") - defer span.End() - - sb := m.stbl. - Select( - "ulid", "object_type", "object_id", "relation", "_user", "operation", - "condition_name", "condition_context", "inserted_at", - ). - From("changelog"). - Where(sq.Eq{"store": store}). - Where(fmt.Sprintf("inserted_at <= datetime('subsec','-%f seconds')", horizonOffset.Seconds())). - OrderBy("ulid asc") - - if objectTypeFilter != "" { - sb = sb.Where(sq.Eq{"object_type": objectTypeFilter}) - } - if opts.From != "" { - token, err := sqlcommon.UnmarshallContToken(opts.From) - if err != nil { - return nil, nil, err - } - if token.ObjectType != objectTypeFilter { - return nil, nil, storage.ErrMismatchObjectType - } - - sb = sb.Where(sq.Gt{"ulid": token.Ulid}) // > as we always return a continuation token. - } - if opts.PageSize > 0 { - sb = sb.Limit(uint64(opts.PageSize)) // + 1 is NOT used here as we always return a continuation token. - } - - rows, err := sb.QueryContext(ctx) - if err != nil { - return nil, nil, handleSQLError(err) - } - defer func() { _ = rows.Close() }() - - var changes []*openfgav1.TupleChange - var ulid string - for rows.Next() { - var objectType, objectID, relation, user string - var operation int - var insertedAt time.Time - var conditionName sql.NullString - var conditionContext []byte - - err = rows.Scan( - &ulid, - &objectType, - &objectID, - &relation, - &user, - &operation, - &conditionName, - &conditionContext, - &insertedAt, - ) - if err != nil { - return nil, nil, handleSQLError(err) - } - - var conditionContextStruct structpb.Struct - if conditionName.String != "" { - if conditionContext != nil { - if err := proto.Unmarshal(conditionContext, &conditionContextStruct); err != nil { - return nil, nil, err - } - } - } - - tk := tupleUtils.NewTupleKeyWithCondition( - tupleUtils.BuildObject(objectType, objectID), - relation, - user, - conditionName.String, - &conditionContextStruct, - ) - - changes = append(changes, &openfgav1.TupleChange{ - TupleKey: tk, - Operation: openfgav1.TupleOperation(operation), - Timestamp: timestamppb.New(insertedAt.UTC()), - }) - } - - if len(changes) == 0 { - return nil, nil, storage.ErrNotFound - } - - contToken, err := json.Marshal(sqlcommon.NewContToken(ulid, objectTypeFilter)) - if err != nil { - return nil, nil, err - } - - return changes, contToken, nil -} - -func (m *SQLite) IsReady(ctx context.Context) (storage.ReadinessStatus, error) { - if err := m.db.PingContext(ctx); err != nil { - return storage.ReadinessStatus{}, err - } - return storage.ReadinessStatus{ - IsReady: true, - }, nil -} - -// SQLite will return an SQLITE_BUSY error when the database is locked rather than waiting for the lock. -// This function retries the operation up to 5 times before returning the error. -func (m *SQLite) busyRetry(fn func() error) error { - for retries := 0; ; retries++ { - err := fn() - if err == nil || retries == m.cfg.QueryRetries { - return err - } - - var sqliteErr *sqlite3.Error - if errors.As(err, &sqliteErr) && (sqliteErr.Code == sqlite3.ErrLocked || sqliteErr.Code == sqlite3.ErrBusy) { - time.Sleep(10 * time.Millisecond) - continue - } - - return err - } -} - -func handleSQLError(err error, args ...any) error { - if strings.Contains(err.Error(), "UNIQUE constraint failed:") { - if len(args) > 0 { - if tk, ok := args[0].(*openfgav1.TupleKey); ok { - return storage.InvalidWriteInputError(tk, openfgav1.TupleOperation_TUPLE_OPERATION_WRITE) - } - } - return storage.ErrCollision - } - - return sqlcommon.HandleSQLError(err, args...) -} diff --git a/pkg/services/authz/zanzana/store/sqlite/store_test.go b/pkg/services/authz/zanzana/store/sqlite/store_test.go deleted file mode 100644 index 5388114d188..00000000000 --- a/pkg/services/authz/zanzana/store/sqlite/store_test.go +++ /dev/null @@ -1,295 +0,0 @@ -package sqlite - -import ( - "context" - "database/sql" - "testing" - "time" - - sq "github.com/Masterminds/squirrel" - "github.com/oklog/ulid/v2" - openfgav1 "github.com/openfga/api/proto/openfga/v1" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/proto" - - "github.com/openfga/openfga/pkg/storage" - "github.com/openfga/openfga/pkg/storage/sqlcommon" - "github.com/openfga/openfga/pkg/storage/test" - "github.com/openfga/openfga/pkg/tuple" - "github.com/openfga/openfga/pkg/typesystem" - - "github.com/grafana/grafana/pkg/infra/db" - "github.com/grafana/grafana/pkg/services/sqlstore/migrator" - "github.com/grafana/grafana/pkg/tests/testsuite" - - zassets "github.com/grafana/grafana/pkg/services/authz/zanzana/store/assets" - "github.com/grafana/grafana/pkg/services/authz/zanzana/store/migration" -) - -func TestMain(m *testing.M) { - testsuite.Run(m) -} - -// TestIntegrationDatastore runs open fga default datastore test suite -func TestIntegrationDatastore(t *testing.T) { - db := sqliteIntegrationTest(t) - ds, err := NewWithDB(db, NewConfig()) - require.NoError(t, err) - test.RunAllTests(t, ds) -} - -// TestIntegrationReadEnsureNoOrder asserts that the read response is not ordered by ulid. -func TestIntegrationReadEnsureNoOrder(t *testing.T) { - db := sqliteIntegrationTest(t) - - ds, err := NewWithDB(db, NewConfig()) - require.NoError(t, err) - - ctx := context.Background() - store := "store" - firstTuple := tuple.NewTupleKey("doc:object_id_1", "relation", "user:user_1") - secondTuple := tuple.NewTupleKey("doc:object_id_2", "relation", "user:user_2") - - err = sqlcommon.Write(ctx, - sqlcommon.NewDBInfo(ds.db, ds.stbl, sq.Expr("datetime('subsec')")), - store, - []*openfgav1.TupleKeyWithoutCondition{}, - []*openfgav1.TupleKey{firstTuple}, - time.Now()) - require.NoError(t, err) - - // Tweak time so that ULID is smaller. - err = sqlcommon.Write(ctx, - sqlcommon.NewDBInfo(ds.db, ds.stbl, sq.Expr("datetime('subsec')")), - store, - []*openfgav1.TupleKeyWithoutCondition{}, - []*openfgav1.TupleKey{secondTuple}, - time.Now().Add(time.Minute*-1)) - require.NoError(t, err) - - iter, err := ds.Read(ctx, - store, - tuple.NewTupleKey("doc:", "relation", "")) - defer iter.Stop() - - require.NoError(t, err) - - // We expect that objectID1 will return first because it is inserted first. - curTuple, err := iter.Next(ctx) - require.NoError(t, err) - require.Equal(t, firstTuple, curTuple.GetKey()) - - curTuple, err = iter.Next(ctx) - require.NoError(t, err) - require.Equal(t, secondTuple, curTuple.GetKey()) -} - -// TestIntegrationReadPageEnsureNoOrder asserts that the read page is ordered by ulid. -func TestIntegrationReadPageEnsureOrder(t *testing.T) { - db := sqliteIntegrationTest(t) - - ds, err := NewWithDB(db, NewConfig()) - require.NoError(t, err) - - ctx := context.Background() - - store := "store" - firstTuple := tuple.NewTupleKey("doc:object_id_1", "relation", "user:user_1") - secondTuple := tuple.NewTupleKey("doc:object_id_2", "relation", "user:user_2") - - err = sqlcommon.Write(ctx, - sqlcommon.NewDBInfo(ds.db, ds.stbl, sq.Expr("datetime('subsec')")), - store, - []*openfgav1.TupleKeyWithoutCondition{}, - []*openfgav1.TupleKey{firstTuple}, - time.Now()) - require.NoError(t, err) - - // Tweak time so that ULID is smaller. - err = sqlcommon.Write(ctx, - sqlcommon.NewDBInfo(ds.db, ds.stbl, sq.Expr("datetime('subsec')")), - store, - []*openfgav1.TupleKeyWithoutCondition{}, - []*openfgav1.TupleKey{secondTuple}, - time.Now().Add(time.Minute*-1)) - require.NoError(t, err) - - tuples, _, err := ds.ReadPage(ctx, - store, - tuple.NewTupleKey("doc:", "relation", ""), - storage.NewPaginationOptions(0, "")) - require.NoError(t, err) - - require.Len(t, tuples, 2) - // We expect that objectID2 will return first because it has a smaller ulid. - require.Equal(t, secondTuple, tuples[0].GetKey()) - require.Equal(t, firstTuple, tuples[1].GetKey()) -} - -func TestIntegrationReadAuthorizationModelUnmarshallError(t *testing.T) { - db := sqliteIntegrationTest(t) - - ds, err := NewWithDB(db, NewConfig()) - require.NoError(t, err) - - ctx := context.Background() - store := "store" - modelID := "foo" - schemaVersion := typesystem.SchemaVersion1_0 - - bytes, err := proto.Marshal(&openfgav1.TypeDefinition{Type: "document"}) - require.NoError(t, err) - pbdata := []byte{0x01, 0x02, 0x03} - - _, err = ds.db.ExecContext(ctx, "INSERT INTO authorization_model (store, authorization_model_id, schema_version, type, type_definition, serialized_protobuf) VALUES (?, ?, ?, ?, ?, ?)", store, modelID, schemaVersion, "document", bytes, pbdata) - require.NoError(t, err) - - _, err = ds.ReadAuthorizationModel(ctx, store, modelID) - require.Error(t, err) - require.Contains(t, err.Error(), "cannot parse invalid wire-format data") -} - -// TestIntegrationAllowNullCondition tests that tuple and changelog rows existing before -// migration 005_add_conditions_to_tuples can be successfully read. -func TestIntegrationAllowNullCondition(t *testing.T) { - db := sqliteIntegrationTest(t) - - ds, err := NewWithDB(db, NewConfig()) - require.NoError(t, err) - - ctx := context.Background() - - stmt := ` - INSERT INTO tuple ( - store, object_type, object_id, relation, _user, user_type, ulid, - condition_name, condition_context, inserted_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('subsec')); - ` - _, err = ds.db.ExecContext( - ctx, stmt, "store", "folder", "2021-budget", "owner", "user:anne", "user", - ulid.Make().String(), nil, nil, - ) - require.NoError(t, err) - - tk := tuple.NewTupleKey("folder:2021-budget", "owner", "user:anne") - iter, err := ds.Read(ctx, "store", tk) - require.NoError(t, err) - defer iter.Stop() - - curTuple, err := iter.Next(ctx) - require.NoError(t, err) - require.Equal(t, tk, curTuple.GetKey()) - - tuples, _, err := ds.ReadPage(ctx, "store", &openfgav1.TupleKey{}, storage.PaginationOptions{ - PageSize: 2, - }) - require.NoError(t, err) - require.Len(t, tuples, 1) - require.Equal(t, tk, tuples[0].GetKey()) - - userTuple, err := ds.ReadUserTuple(ctx, "store", tk) - require.NoError(t, err) - require.Equal(t, tk, userTuple.GetKey()) - - tk2 := tuple.NewTupleKey("folder:2022-budget", "viewer", "user:anne") - _, err = ds.db.ExecContext( - ctx, stmt, "store", "folder", "2022-budget", "viewer", "user:anne", "userset", - ulid.Make().String(), nil, nil, - ) - - require.NoError(t, err) - iter, err = ds.ReadUsersetTuples(ctx, "store", storage.ReadUsersetTuplesFilter{Object: "folder:2022-budget"}) - require.NoError(t, err) - defer iter.Stop() - - curTuple, err = iter.Next(ctx) - require.NoError(t, err) - require.Equal(t, tk2, curTuple.GetKey()) - - iter, err = ds.ReadStartingWithUser(ctx, "store", storage.ReadStartingWithUserFilter{ - ObjectType: "folder", - Relation: "owner", - UserFilter: []*openfgav1.ObjectRelation{ - {Object: "user:anne"}, - }, - }) - require.NoError(t, err) - defer iter.Stop() - - curTuple, err = iter.Next(ctx) - require.NoError(t, err) - require.Equal(t, tk, curTuple.GetKey()) - - stmt = ` - INSERT INTO changelog ( - store, object_type, object_id, relation, _user, ulid, - condition_name, condition_context, inserted_at, operation - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('subsec'), ?); -` - _, err = ds.db.ExecContext( - ctx, stmt, "store", "folder", "2021-budget", "owner", "user:anne", - ulid.Make().String(), nil, nil, openfgav1.TupleOperation_TUPLE_OPERATION_WRITE, - ) - require.NoError(t, err) - - _, err = ds.db.ExecContext( - ctx, stmt, "store", "folder", "2021-budget", "owner", "user:anne", - ulid.Make().String(), nil, nil, openfgav1.TupleOperation_TUPLE_OPERATION_DELETE, - ) - require.NoError(t, err) - - changes, _, err := ds.ReadChanges(ctx, "store", "folder", storage.PaginationOptions{}, 0) - require.NoError(t, err) - require.Len(t, changes, 2) - require.Equal(t, tk, changes[0].GetTupleKey()) - require.Equal(t, tk, changes[1].GetTupleKey()) -} - -// TestIntegrationMarshalledAssertions tests that previously persisted marshalled -// assertions can be read back. In any case where the Assertions proto model -// needs to change, we'll likely need to introduce a series of data migrations. -func TestIntegrationMarshalledAssertions(t *testing.T) { - db := sqliteIntegrationTest(t) - - ds, err := NewWithDB(db, NewConfig()) - require.NoError(t, err) - - ctx := context.Background() - // Note: this represents an assertion written on v1.3.7. - stmt := ` - INSERT INTO assertion ( - store, authorization_model_id, assertions - ) VALUES (?, ?, UNHEX('0A2B0A270A12666F6C6465723A323032312D62756467657412056F776E65721A0A757365723A616E6E657A1001')); - ` - _, err = ds.db.ExecContext(ctx, stmt, "store", "model") - require.NoError(t, err) - - assertions, err := ds.ReadAssertions(ctx, "store", "model") - require.NoError(t, err) - - expectedAssertions := []*openfgav1.Assertion{ - { - TupleKey: &openfgav1.AssertionTupleKey{ - Object: "folder:2021-budget", - Relation: "owner", - User: "user:annez", - }, - Expectation: true, - }, - } - require.Equal(t, expectedAssertions, assertions) -} - -func sqliteIntegrationTest(tb testing.TB) *sql.DB { - if testing.Short() || !db.IsTestDbSQLite() { - tb.Skip("skipping integration test") - } - - db, cfg := db.InitTestDBWithCfg(tb) - - m := migrator.NewMigrator(db.GetEngine(), cfg) - err := migration.RunWithMigrator(m, cfg, zassets.EmbedMigrations, zassets.SQLiteMigrationDir) - require.NoError(tb, err) - - return db.GetEngine().DB().DB -} diff --git a/pkg/services/authz/zanzana/store/sqlite/write.go b/pkg/services/authz/zanzana/store/sqlite/write.go deleted file mode 100644 index 4e5dd79610b..00000000000 --- a/pkg/services/authz/zanzana/store/sqlite/write.go +++ /dev/null @@ -1,165 +0,0 @@ -package sqlite - -import ( - "context" - "database/sql" - "time" - - sq "github.com/Masterminds/squirrel" - "github.com/oklog/ulid/v2" - "google.golang.org/protobuf/proto" - - openfgav1 "github.com/openfga/api/proto/openfga/v1" - "github.com/openfga/openfga/pkg/storage" - tupleUtils "github.com/openfga/openfga/pkg/tuple" -) - -// write is copied from https://github.com/openfga/openfga/blob/main/pkg/storage/sqlcommon/sqlcommon.go#L330-L456 -// but uses custom handleSQLError. -func write( - ctx context.Context, - db *sql.DB, - stbl sq.StatementBuilderType, - sqlTime any, - store string, - deletes storage.Deletes, - writes storage.Writes, - now time.Time, -) error { - txn, err := db.BeginTx(ctx, nil) - if err != nil { - return handleSQLError(err) - } - defer func() { - _ = txn.Rollback() - }() - - changelogBuilder := stbl. - Insert("changelog"). - Columns( - "store", "object_type", "object_id", "relation", "_user", - "condition_name", "condition_context", "operation", "ulid", "inserted_at", - ) - - deleteBuilder := stbl.Delete("tuple") - - for _, tk := range deletes { - id := ulid.MustNew(ulid.Timestamp(now), ulid.DefaultEntropy()).String() - objectType, objectID := tupleUtils.SplitObject(tk.GetObject()) - - res, err := deleteBuilder. - Where(sq.Eq{ - "store": store, - "object_type": objectType, - "object_id": objectID, - "relation": tk.GetRelation(), - "_user": tk.GetUser(), - "user_type": tupleUtils.GetUserTypeFromUser(tk.GetUser()), - }). - RunWith(txn). // Part of a txn. - ExecContext(ctx) - if err != nil { - return handleSQLError(err, tk) - } - - rowsAffected, err := res.RowsAffected() - if err != nil { - return handleSQLError(err) - } - - if rowsAffected != 1 { - return storage.InvalidWriteInputError( - tk, - openfgav1.TupleOperation_TUPLE_OPERATION_DELETE, - ) - } - - changelogBuilder = changelogBuilder.Values( - store, objectType, objectID, - tk.GetRelation(), tk.GetUser(), - "", nil, // Redact condition info for deletes since we only need the base triplet (object, relation, user). - openfgav1.TupleOperation_TUPLE_OPERATION_DELETE, - id, sqlTime, - ) - } - - insertBuilder := stbl. - Insert("tuple"). - Columns( - "store", "object_type", "object_id", "relation", "_user", "user_type", - "condition_name", "condition_context", "ulid", "inserted_at", - ) - - for _, tk := range writes { - id := ulid.MustNew(ulid.Timestamp(now), ulid.DefaultEntropy()).String() - objectType, objectID := tupleUtils.SplitObject(tk.GetObject()) - - conditionName, conditionContext, err := marshalRelationshipCondition(tk.GetCondition()) - if err != nil { - return err - } - - _, err = insertBuilder. - Values( - store, - objectType, - objectID, - tk.GetRelation(), - tk.GetUser(), - tupleUtils.GetUserTypeFromUser(tk.GetUser()), - conditionName, - conditionContext, - id, - sqlTime, - ). - RunWith(txn). // Part of a txn. - ExecContext(ctx) - if err != nil { - return handleSQLError(err, tk) - } - - changelogBuilder = changelogBuilder.Values( - store, - objectType, - objectID, - tk.GetRelation(), - tk.GetUser(), - conditionName, - conditionContext, - openfgav1.TupleOperation_TUPLE_OPERATION_WRITE, - id, - sqlTime, - ) - } - - if len(writes) > 0 || len(deletes) > 0 { - _, err := changelogBuilder.RunWith(txn).ExecContext(ctx) // Part of a txn. - if err != nil { - return handleSQLError(err) - } - } - - if err := txn.Commit(); err != nil { - return handleSQLError(err) - } - - return nil -} - -// copied from https://github.com/openfga/openfga/blob/main/pkg/storage/sqlcommon/encoding.go#L8-L24 -func marshalRelationshipCondition( - rel *openfgav1.RelationshipCondition, -) (name string, context []byte, err error) { - if rel != nil { - if rel.GetContext() != nil && len(rel.GetContext().GetFields()) > 0 { - context, err = proto.Marshal(rel.GetContext()) - if err != nil { - return name, context, err - } - } - - return rel.GetName(), context, err - } - - return name, context, err -} diff --git a/pkg/services/authz/zanzana/store/store.go b/pkg/services/authz/zanzana/store/store.go index fe7aadc0cee..360e75c2dd4 100644 --- a/pkg/services/authz/zanzana/store/store.go +++ b/pkg/services/authz/zanzana/store/store.go @@ -2,15 +2,15 @@ package store import ( "fmt" + "strings" "time" - "xorm.io/xorm" - "github.com/openfga/openfga/assets" "github.com/openfga/openfga/pkg/storage" "github.com/openfga/openfga/pkg/storage/mysql" "github.com/openfga/openfga/pkg/storage/postgres" "github.com/openfga/openfga/pkg/storage/sqlcommon" + "github.com/openfga/openfga/pkg/storage/sqlite" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" @@ -19,9 +19,7 @@ import ( "github.com/grafana/grafana/pkg/setting" zlogger "github.com/grafana/grafana/pkg/services/authz/zanzana/logger" - zassets "github.com/grafana/grafana/pkg/services/authz/zanzana/store/assets" "github.com/grafana/grafana/pkg/services/authz/zanzana/store/migration" - "github.com/grafana/grafana/pkg/services/authz/zanzana/store/sqlite" ) func NewStore(cfg *setting.Cfg, logger log.Logger) (storage.OpenFGADatastore, error) { @@ -32,22 +30,12 @@ func NewStore(cfg *setting.Cfg, logger log.Logger) (storage.OpenFGADatastore, er switch grafanaDBCfg.Type { case migrator.SQLite: - connStr := grafanaDBCfg.ConnectionString - // Initilize connection using xorm engine so we can reuse it for both migrations and data store - engine, err := xorm.NewEngine(grafanaDBCfg.Type, connStr) - if err != nil { - return nil, fmt.Errorf("failed to connect to database: %w", err) - } - - m := migrator.NewMigrator(engine, cfg) - if err := migration.RunWithMigrator(m, cfg, zassets.EmbedMigrations, zassets.SQLiteMigrationDir); err != nil { + connStr := sqliteConnectionString(grafanaDBCfg.ConnectionString) + if err := migration.Run(cfg, migrator.SQLite, connStr, assets.EmbedMigrations, assets.SqliteMigrationDir); err != nil { return nil, fmt.Errorf("failed to run migrations: %w", err) } - return sqlite.NewWithDB(engine.DB().DB, &sqlite.Config{ - Config: zanzanaDBCfg, - QueryRetries: grafanaDBCfg.QueryRetries, - }) + return sqlite.New(connStr, zanzanaDBCfg) case migrator.MySQL: // For mysql we need to pass parseTime parameter in connection string connStr := grafanaDBCfg.ConnectionString + "&parseTime=true" @@ -75,20 +63,16 @@ func NewEmbeddedStore(cfg *setting.Cfg, db db.DB, logger log.Logger) (storage.Op return nil, fmt.Errorf("failed to parse database config: %w", err) } - m := migrator.NewMigrator(db.GetEngine(), cfg) - switch grafanaDBCfg.Type { case migrator.SQLite: - if err := migration.RunWithMigrator(m, cfg, zassets.EmbedMigrations, zassets.SQLiteMigrationDir); err != nil { + grafanaDBCfg.ConnectionString = sqliteConnectionString(grafanaDBCfg.ConnectionString) + if err := migration.Run(cfg, migrator.SQLite, grafanaDBCfg.ConnectionString, assets.EmbedMigrations, assets.SqliteMigrationDir); err != nil { return nil, fmt.Errorf("failed to run migrations: %w", err) } - // FIXME(kalleep): We should work on getting sqlite implemtation merged upstream and replace this one - return sqlite.NewWithDB(db.GetEngine().DB().DB, &sqlite.Config{ - Config: zanzanaDBCfg, - QueryRetries: grafanaDBCfg.QueryRetries, - }) + return sqlite.New(grafanaDBCfg.ConnectionString, zanzanaDBCfg) case migrator.MySQL: + m := migrator.NewMigrator(db.GetEngine(), cfg) if err := migration.RunWithMigrator(m, cfg, assets.EmbedMigrations, assets.MySQLMigrationDir); err != nil { return nil, fmt.Errorf("failed to run migrations: %w", err) } @@ -96,6 +80,7 @@ func NewEmbeddedStore(cfg *setting.Cfg, db db.DB, logger log.Logger) (storage.Op // For mysql we need to pass parseTime parameter in connection string return mysql.New(grafanaDBCfg.ConnectionString+"&parseTime=true", zanzanaDBCfg) case migrator.Postgres: + m := migrator.NewMigrator(db.GetEngine(), cfg) if err := migration.RunWithMigrator(m, cfg, assets.EmbedMigrations, assets.PostgresMigrationDir); err != nil { return nil, fmt.Errorf("failed to run migrations: %w", err) } @@ -126,3 +111,8 @@ func parseConfig(cfg *setting.Cfg, logger log.Logger) (*sqlstore.DatabaseConfig, return grafanaDBCfg, zanzanaDBCfg, nil } + +func sqliteConnectionString(v string) string { + // hardcode zanzana.db for now + return v[0:strings.LastIndex(v, "/")+1] + "zanzana.db" +} diff --git a/pkg/services/authz/zanzana/zanzana.go b/pkg/services/authz/zanzana/zanzana.go index 03bfe9f36ed..ece56e98179 100644 --- a/pkg/services/authz/zanzana/zanzana.go +++ b/pkg/services/authz/zanzana/zanzana.go @@ -118,3 +118,8 @@ func TranslateFixedRole(role string) string { role = strings.ReplaceAll(role, ".", "_") return role } + +// Translate "read" for the dashboard into "dashboard_read" for folder +func TranslateToFolderRelation(relation, objectType string) string { + return fmt.Sprintf("%s_%s", objectType, relation) +} diff --git a/pkg/services/dashboards/service/zanzana.go b/pkg/services/dashboards/service/zanzana.go index f7bfb4d23f6..7441df302cf 100644 --- a/pkg/services/dashboards/service/zanzana.go +++ b/pkg/services/dashboards/service/zanzana.go @@ -10,10 +10,11 @@ import ( "github.com/prometheus/client_golang/prometheus" + "github.com/grafana/authlib/claims" + "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/authz/zanzana" "github.com/grafana/grafana/pkg/services/dashboards" - "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" ) const ( @@ -193,9 +194,16 @@ func (dr *DashboardServiceImpl) checkDashboards(ctx context.Context, query dashb } req := accesscontrol.CheckRequest{ - User: query.SignedInUser.GetUID(), - Relation: "read", - Object: zanzana.NewScopedTupleEntry(objectType, d.UID, "", strconv.FormatInt(orgId, 10)), + Namespace: claims.OrgNamespaceFormatter(orgId), + User: query.SignedInUser.GetUID(), + Relation: "read", + Object: zanzana.NewScopedTupleEntry(objectType, d.UID, "", strconv.FormatInt(orgId, 10)), + } + + if objectType != zanzana.TypeFolder { + // Pass parentn folder for the correct check + req.Parent = d.FolderUID + req.ObjectType = objectType } allowed, err := dr.ac.Check(ctx, req) @@ -238,45 +246,47 @@ func (dr *DashboardServiceImpl) findDashboardsZanzanaList(ctx context.Context, q ctx, span := tracer.Start(ctx, "dashboards.service.findDashboardsZanzanaList") defer span.End() - resourceUIDs, err := dr.listUserResources(ctx, query) - if err != nil { - return nil, err - } - if len(resourceUIDs) == 0 { - return []dashboards.DashboardSearchProjection{}, nil - } + var result []dashboards.DashboardSearchProjection - query.DashboardUIDs = resourceUIDs - query.SkipAccessControlFilter = true - return dr.dashboardStore.FindDashboards(ctx, &query) -} - -func (dr *DashboardServiceImpl) listUserResources(ctx context.Context, query dashboards.FindPersistedDashboardsQuery) ([]string, error) { - tasks := make([]func() ([]string, error), 0) - var resourceTypes []string - - // For some search types we need dashboards or folders only - switch query.Type { - case searchstore.TypeDashboard: - resourceTypes = []string{zanzana.TypeDashboard} - case searchstore.TypeFolder, searchstore.TypeAlertFolder: - resourceTypes = []string{zanzana.TypeFolder} - default: - resourceTypes = []string{zanzana.TypeDashboard, zanzana.TypeFolder} - } - - for _, resourceType := range resourceTypes { - tasks = append(tasks, func() ([]string, error) { - return dr.listAllowedResources(ctx, query, resourceType) - }) - } - - uids, err := runBatch(tasks) + allowedFolders, err := dr.listAllowedResources(ctx, query, zanzana.TypeFolder) if err != nil { return nil, err } - return uids, nil + if len(allowedFolders) > 0 { + // Find dashboards in folders that user has access to + query.SkipAccessControlFilter = true + query.FolderUIDs = allowedFolders + result, err = dr.dashboardStore.FindDashboards(ctx, &query) + if err != nil { + return nil, err + } + } + + // skip if limit reached + rest := query.Limit - int64(len(result)) + if rest <= 0 { + return result, nil + } + + // Run second query to find dashboards with direct permission assignments + allowedDashboards, err := dr.listAllowedResources(ctx, query, zanzana.TypeDashboard) + if err != nil { + return nil, err + } + + if len(allowedDashboards) > 0 { + query.FolderUIDs = []string{} + query.DashboardUIDs = allowedDashboards + query.Limit = rest + dashboardRes, err := dr.dashboardStore.FindDashboards(ctx, &query) + if err != nil { + return nil, err + } + result = append(result, dashboardRes...) + } + + return result, err } func (dr *DashboardServiceImpl) listAllowedResources(ctx context.Context, query dashboards.FindPersistedDashboardsQuery, resourceType string) ([]string, error) { @@ -307,36 +317,3 @@ func (dr *DashboardServiceImpl) listAllowedResources(ctx context.Context, query return resourceUIDs, nil } - -func runBatch(tasks []func() ([]string, error)) ([]string, error) { - var wg sync.WaitGroup - tasksNum := len(tasks) - resChan := make(chan []string, tasksNum) - errChan := make(chan error, tasksNum) - - for _, task := range tasks { - wg.Add(1) - go func() { - defer wg.Done() - res, err := task() - resChan <- res - errChan <- err - }() - } - - wg.Wait() - close(resChan) - close(errChan) - - for err := range errChan { - if err != nil { - return nil, err - } - } - - result := make([]string, 0) - for res := range resChan { - result = append(result, res...) - } - return result, nil -} diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index ba68086a0af..1d289b98596 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1255,10 +1255,11 @@ var ( }, { Name: "dashboardRestore", - Description: "Enables deleted dashboard restore feature (backend only)", + Description: "Enables deleted dashboard restore feature", Stage: FeatureStageExperimental, Owner: grafanaSearchAndStorageSquad, HideFromAdminPage: true, + Expression: "false", // enabled by default }, { Name: "datasourceProxyDisableRBAC", @@ -1367,13 +1368,6 @@ var ( Owner: grafanaAlertingSquad, RequiresRestart: true, }, - { - Name: "dashboardRestoreUI", - Description: "Enables the frontend to be able to restore a recently deleted dashboard", - Stage: FeatureStageExperimental, - Owner: grafanaFrontendPlatformSquad, - Expression: "false", // enabled by default - }, { Name: "cloudWatchRoundUpEndTime", Description: "Round up end time for metric queries to the next minute to avoid missing data", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 27dc4bcdf2a..3dd5ee565ce 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -179,7 +179,6 @@ failWrongDSUID,experimental,@grafana/plugins-platform-backend,false,false,false zanzana,experimental,@grafana/identity-access-team,false,false,false passScopeToDashboardApi,experimental,@grafana/dashboards-squad,false,false,false alertingApiServer,experimental,@grafana/alerting-squad,false,true,false -dashboardRestoreUI,experimental,@grafana/grafana-frontend-platform,false,false,false cloudWatchRoundUpEndTime,GA,@grafana/aws-datasources,false,false,false cloudwatchMetricInsightsCrossAccount,preview,@grafana/aws-datasources,false,false,true prometheusAzureOverrideAudience,deprecated,@grafana/partner-datasources,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 147d0f4681e..1aee2335aee 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -668,7 +668,7 @@ const ( FlagNotificationBanner = "notificationBanner" // FlagDashboardRestore - // Enables deleted dashboard restore feature (backend only) + // Enables deleted dashboard restore feature FlagDashboardRestore = "dashboardRestore" // FlagDatasourceProxyDisableRBAC @@ -727,10 +727,6 @@ const ( // Register Alerting APIs with the K8s API server FlagAlertingApiServer = "alertingApiServer" - // FlagDashboardRestoreUI - // Enables the frontend to be able to restore a recently deleted dashboard - FlagDashboardRestoreUI = "dashboardRestoreUI" - // FlagCloudWatchRoundUpEndTime // Round up end time for metric queries to the next minute to avoid missing data FlagCloudWatchRoundUpEndTime = "cloudWatchRoundUpEndTime" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index fbd7b6e335a..52088db76da 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -808,17 +808,18 @@ { "metadata": { "name": "dashboardRestore", - "resourceVersion": "1719321877748", + "resourceVersion": "1728397491294", "creationTimestamp": "2024-05-16T17:36:26Z", "annotations": { - "grafana.app/updatedTimestamp": "2024-06-25 13:24:37.748284 +0000 UTC" + "grafana.app/updatedTimestamp": "2024-10-08 14:24:51.294668 +0000 UTC" } }, "spec": { - "description": "Enables deleted dashboard restore feature (backend only)", + "description": "Enables deleted dashboard restore feature", "stage": "experimental", "codeowner": "@grafana/search-and-storage", - "hideFromAdminPage": true + "hideFromAdminPage": true, + "expression": "false" } }, { @@ -826,6 +827,7 @@ "name": "dashboardRestoreUI", "resourceVersion": "1720021873452", "creationTimestamp": "2024-06-25T14:43:13Z", + "deletionTimestamp": "2024-10-08T14:24:51Z", "annotations": { "grafana.app/updatedTimestamp": "2024-07-03 15:51:13.452477 +0000 UTC" } @@ -3087,6 +3089,20 @@ "codeowner": "@grafana/identity-access-team" } }, + { + "metadata": { + "name": "unifiedStorageSearch", + "resourceVersion": "1726771421439", + "creationTimestamp": "2024-09-19T18:43:41Z" + }, + "spec": { + "description": "Enable unified storage search", + "stage": "experimental", + "codeowner": "@grafana/search-and-storage", + "hideFromAdminPage": true, + "hideFromDocs": true + } + }, { "metadata": { "name": "vizActions", @@ -3141,4 +3157,4 @@ } } ] -} \ No newline at end of file +} diff --git a/pkg/services/folder/folderimpl/folder.go b/pkg/services/folder/folderimpl/folder.go index 36f21e37170..a1608f85e71 100644 --- a/pkg/services/folder/folderimpl/folder.go +++ b/pkg/services/folder/folderimpl/folder.go @@ -260,6 +260,7 @@ func (s *Service) Get(ctx context.Context, q *folder.GetFolderQuery) (*folder.Fo if !s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) { dashFolder.Fullpath = dashFolder.Title + dashFolder.FullpathUIDs = dashFolder.UID return dashFolder, nil } @@ -282,7 +283,8 @@ func (s *Service) Get(ctx context.Context, q *folder.GetFolderQuery) (*folder.Fo f.Version = dashFolder.Version if !s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) { - f.Fullpath = f.Title // set full path to the folder title (unescaped) + f.Fullpath = f.Title // set full path to the folder title (unescaped) + f.FullpathUIDs = f.UID // set full path to the folder UID } return f, err @@ -671,6 +673,30 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) ( return nil, err } + if s.features.IsEnabled(ctx, featuremgmt.FlagKubernetesFolders) { + if f.ParentUID == "" { + return f, nil + } + + // Fetch the parent since the permissions for fetching the newly created folder + // are not yet present for the user--this requires a call to ClearUserPermissionCache + parent, err := s.Get(ctx, &folder.GetFolderQuery{ + UID: &f.ParentUID, + OrgID: f.OrgID, + WithFullpath: true, + WithFullpathUIDs: true, + SignedInUser: user, + }) + if err != nil { + return nil, err + } + // #TODO revisit setting permissions so that we can centralise the logic for escaping slashes in titles + // Escape forward slashes in the title + title := strings.Replace(f.Title, "/", "\\/", -1) + f.Fullpath = title + "/" + parent.Fullpath + f.FullpathUIDs = f.UID + "/" + parent.FullpathUIDs + } + return f, nil } diff --git a/pkg/services/folder/folderimpl/folder_test.go b/pkg/services/folder/folderimpl/folder_test.go index 283dcb668c1..188c4f57fce 100644 --- a/pkg/services/folder/folderimpl/folder_test.go +++ b/pkg/services/folder/folderimpl/folder_test.go @@ -501,7 +501,7 @@ func TestIntegrationNestedFolderService(t *testing.T) { lps, err := librarypanels.ProvideService(cfg, db, routeRegister, elementService, serviceWithFlagOn) require.NoError(t, err) - ancestors := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "getDescendantCountsOn", createCmd) + ancestors := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "getDescendantCountsOn", createCmd, true) parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestors[0].UID) require.NoError(t, err) @@ -584,7 +584,7 @@ func TestIntegrationNestedFolderService(t *testing.T) { lps, err := librarypanels.ProvideService(cfg, db, routeRegister, elementService, serviceWithFlagOff) require.NoError(t, err) - ancestors := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "getDescendantCountsOff", createCmd) + ancestors := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "getDescendantCountsOff", createCmd, true) parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestors[0].UID) require.NoError(t, err) @@ -725,7 +725,7 @@ func TestIntegrationNestedFolderService(t *testing.T) { alertStore, err := ngstore.ProvideDBStore(cfg, tc.featuresFlag, db, tc.service, dashSrv, ac) require.NoError(t, err) - ancestors := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, tc.depth, tc.prefix, createCmd) + ancestors := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, tc.depth, tc.prefix, createCmd, true) parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestors[0].UID) require.NoError(t, err) @@ -1536,8 +1536,8 @@ func TestIntegrationNestedFolderSharedWithMe(t *testing.T) { t.Run("Should get folders shared with given user", func(t *testing.T) { depth := 3 - ancestorFoldersWithPermissions := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "withPermissions", createCmd) - ancestorFoldersWithoutPermissions := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "withoutPermissions", createCmd) + ancestorFoldersWithPermissions := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "withPermissions", createCmd, true) + ancestorFoldersWithoutPermissions := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "withoutPermissions", createCmd, true) parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorFoldersWithoutPermissions[0].UID) require.NoError(t, err) @@ -1661,8 +1661,8 @@ func TestIntegrationNestedFolderSharedWithMe(t *testing.T) { // tree2-folder-0 // └──tree2-folder-1 // └──tree2-folder-2 - tree1 := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "tree1-", createCmd) - tree2 := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "tree2-", createCmd) + tree1 := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "tree1-", createCmd, true) + tree2 := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "tree2-", createCmd, true) signedInUser.Permissions[orgID][dashboards.ActionFoldersRead] = []string{ // Add permission to tree1-folder-0 @@ -1928,14 +1928,16 @@ func TestFolderServiceGetFolder(t *testing.T) { } depth := 3 - folders := CreateSubtreeInStore(t, folderSvcOn.store, &folderSvcOn, depth, "get/folder-", createCmd) + folders := CreateSubtreeInStore(t, folderSvcOn.store, &folderSvcOn, depth, "get/folder-", createCmd, false) f := folders[1] testCases := []struct { - name string - svc *Service - WithFullpath bool - expectedFullpath string + name string + svc *Service + WithFullpath bool + WithFullpathUIDs bool + expectedFullpath string + expectedFullpathUIDs string }{ { name: "when flag is off", @@ -1954,6 +1956,18 @@ func TestFolderServiceGetFolder(t *testing.T) { WithFullpath: true, expectedFullpath: "get\\/folder-folder-0/get\\/folder-folder-1", }, + { + name: "when flag is on and WithFullpathUIDs is false", + svc: &folderSvcOn, + WithFullpathUIDs: false, + expectedFullpathUIDs: "", + }, + { + name: "when flag is on and WithFullpathUIDs is true", + svc: &folderSvcOn, + WithFullpathUIDs: true, + expectedFullpathUIDs: "uidfor-0/uidfor-1", + }, } for _, tc := range testCases { @@ -2021,7 +2035,7 @@ func TestFolderServiceGetFolders(t *testing.T) { }) prefix := "getfolders/ff/off" - folders := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOff, 5, prefix, createCmd) + folders := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOff, 5, prefix, createCmd, true) f := folders[rand.Intn(len(folders))] t.Run("when flag is off", func(t *testing.T) { @@ -2510,7 +2524,7 @@ func TestSupportBundle(t *testing.T) { } } -func CreateSubtreeInStore(t *testing.T, store folder.Store, service *Service, depth int, prefix string, cmd folder.CreateFolderCommand) []*folder.Folder { +func CreateSubtreeInStore(t *testing.T, store folder.Store, service *Service, depth int, prefix string, cmd folder.CreateFolderCommand, randomUID bool) []*folder.Folder { t.Helper() folders := make([]*folder.Folder, 0, depth) @@ -2518,6 +2532,9 @@ func CreateSubtreeInStore(t *testing.T, store folder.Store, service *Service, de title := fmt.Sprintf("%sfolder-%d", prefix, i) cmd.Title = title cmd.UID = util.GenerateShortUID() + if !randomUID { + cmd.UID = fmt.Sprintf("uidfor-%d", i) + } cmd.OrgID = orgID cmd.SignedInUser = &user.SignedInUser{OrgID: orgID, Permissions: map[int64]map[string][]string{orgID: {dashboards.ActionFoldersCreate: {dashboards.ScopeFoldersAll}}}} diff --git a/pkg/services/folder/folderimpl/sqlstore.go b/pkg/services/folder/folderimpl/sqlstore.go index 2e085fddccd..1428bd037c6 100644 --- a/pkg/services/folder/folderimpl/sqlstore.go +++ b/pkg/services/folder/folderimpl/sqlstore.go @@ -201,8 +201,11 @@ func (ss *FolderStoreImpl) Get(ctx context.Context, q folder.GetFolderQuery) (*f if q.WithFullpath { s.WriteString(fmt.Sprintf(`, %s AS fullpath`, getFullpathSQL(ss.db.GetDialect()))) } + if q.WithFullpathUIDs { + s.WriteString(fmt.Sprintf(`, %s AS fullpath_uids`, getFullapathUIDsSQL(ss.db.GetDialect()))) + } s.WriteString(" FROM folder f0") - if q.WithFullpath { + if q.WithFullpath || q.WithFullpathUIDs { s.WriteString(getFullpathJoinsSQL()) } switch { @@ -241,6 +244,7 @@ func (ss *FolderStoreImpl) Get(ctx context.Context, q folder.GetFolderQuery) (*f }) foldr.Fullpath = strings.TrimLeft(foldr.Fullpath, "/") + foldr.FullpathUIDs = strings.TrimLeft(foldr.FullpathUIDs, "/") return foldr.WithURL(), err } diff --git a/pkg/services/folder/folderimpl/sqlstore_test.go b/pkg/services/folder/folderimpl/sqlstore_test.go index dc60826d618..7d639b92e8b 100644 --- a/pkg/services/folder/folderimpl/sqlstore_test.go +++ b/pkg/services/folder/folderimpl/sqlstore_test.go @@ -485,6 +485,24 @@ func TestIntegrationGet(t *testing.T) { assert.NotEmpty(t, ff.Updated) assert.NotEmpty(t, ff.URL) }) + + t.Run("get folder withFullpathUIDs should set fullpathUIDs as expected", func(t *testing.T) { + ff, err := folderStore.Get(context.Background(), folder.GetFolderQuery{ + UID: &subfolderWithSameName.UID, + OrgID: orgID, + WithFullpathUIDs: true, + }) + require.NoError(t, err) + assert.Equal(t, subfolderWithSameName.UID, ff.UID) + assert.Equal(t, subfolderWithSameName.OrgID, ff.OrgID) + assert.Equal(t, subfolderWithSameName.Title, ff.Title) + assert.Equal(t, subfolderWithSameName.Description, ff.Description) + assert.Equal(t, path.Join(f.UID, subfolderWithSameName.UID), ff.FullpathUIDs) + assert.Equal(t, f.UID, ff.ParentUID) + assert.NotEmpty(t, ff.Created) + assert.NotEmpty(t, ff.Updated) + assert.NotEmpty(t, ff.URL) + }) } func TestIntegrationGetParents(t *testing.T) { diff --git a/pkg/services/folder/model.go b/pkg/services/folder/model.go index b0869713f30..378127516c0 100644 --- a/pkg/services/folder/model.go +++ b/pkg/services/folder/model.go @@ -148,11 +148,12 @@ type DeleteFolderCommand struct { type GetFolderQuery struct { UID *string // Deprecated: use FolderUID instead - ID *int64 - Title *string - ParentUID *string - OrgID int64 - WithFullpath bool + ID *int64 + Title *string + ParentUID *string + OrgID int64 + WithFullpath bool + WithFullpathUIDs bool SignedInUser identity.Requester `json:"-"` } diff --git a/pkg/services/login/model.go b/pkg/services/login/model.go index b11ec2161f3..3d755a80301 100644 --- a/pkg/services/login/model.go +++ b/pkg/services/login/model.go @@ -27,6 +27,7 @@ type UserAuth struct { type ExternalUserInfo struct { OAuthToken *oauth2.Token + SAMLSession *SAMLSession AuthModule string AuthId string UserId int64 @@ -40,6 +41,11 @@ type ExternalUserInfo struct { SkipTeamSync bool } +type SAMLSession struct { + NameID string + SessionIndex string +} + func (e *ExternalUserInfo) String() string { isGrafanaAdmin := "nil" if e.IsGrafanaAdmin != nil { diff --git a/pkg/services/navtree/navtreeimpl/admin.go b/pkg/services/navtree/navtreeimpl/admin.go index 5a4d9cd2335..18a71f24dde 100644 --- a/pkg/services/navtree/navtreeimpl/admin.go +++ b/pkg/services/navtree/navtreeimpl/admin.go @@ -11,8 +11,10 @@ import ( "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol" "github.com/grafana/grafana/pkg/services/serviceaccounts" + "github.com/grafana/grafana/pkg/setting" ) +// nolint: gocyclo func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink, error) { var configNodes []*navtree.NavLink ctx := c.Req.Context() @@ -103,6 +105,16 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink }) } + if s.cfg.Env == setting.Dev { + pluginsNodeLinks = append(pluginsNodeLinks, &navtree.NavLink{ + Text: "Extensions", + Icon: "plug", + SubTitle: "Extend the UI of plugins and Grafana", + Id: "extensions", + Url: s.cfg.AppSubURL + "/admin/extensions", + }) + } + pluginsNode := &navtree.NavLink{ Text: "Plugins and data", SubTitle: "Install plugins and define the relationships between data", diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go index e4bdfcf2bfc..4441b75d658 100644 --- a/pkg/services/navtree/navtreeimpl/navtree.go +++ b/pkg/services/navtree/navtreeimpl/navtree.go @@ -389,7 +389,7 @@ func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext) []*navt }) } - if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagDashboardRestoreUI) && (c.SignedInUser.GetOrgRole() == org.RoleAdmin || c.IsGrafanaAdmin) { + if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagDashboardRestore) && (c.SignedInUser.GetOrgRole() == org.RoleAdmin || c.IsGrafanaAdmin) { dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{ Text: "Recently deleted", SubTitle: "Any items listed here for more than 30 days will be automatically deleted.", diff --git a/pkg/services/ngalert/api/api_provisioning.go b/pkg/services/ngalert/api/api_provisioning.go index f22cf445ce5..ccdfde76c75 100644 --- a/pkg/services/ngalert/api/api_provisioning.go +++ b/pkg/services/ngalert/api/api_provisioning.go @@ -53,7 +53,7 @@ type TemplateService interface { type NotificationPolicyService interface { GetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, string, error) UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p alerting_models.Provenance, version string) error - ResetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) + ResetPolicyTree(ctx context.Context, orgID int64, provenance alerting_models.Provenance) (definitions.Route, error) } type MuteTimingService interface { @@ -84,7 +84,7 @@ func (srv *ProvisioningSrv) RouteGetPolicyTree(c *contextmodel.ReqContext) respo return ErrResp(http.StatusNotFound, err, "") } if err != nil { - return ErrResp(http.StatusInternalServerError, err, "") + return response.ErrOrFallback(http.StatusInternalServerError, "failed to get notification policy tree", err) } return response.JSON(http.StatusOK, policies) @@ -117,16 +117,17 @@ func (srv *ProvisioningSrv) RoutePutPolicyTree(c *contextmodel.ReqContext, tree return ErrResp(http.StatusBadRequest, err, "") } if err != nil { - return response.ErrOrFallback(http.StatusInternalServerError, "", err) + return response.ErrOrFallback(http.StatusInternalServerError, "failed to update notification policy tree", err) } return response.JSON(http.StatusAccepted, util.DynMap{"message": "policies updated"}) } func (srv *ProvisioningSrv) RouteResetPolicyTree(c *contextmodel.ReqContext) response.Response { - tree, err := srv.policies.ResetPolicyTree(c.Req.Context(), c.SignedInUser.GetOrgID()) + provenance := determineProvenance(c) + tree, err := srv.policies.ResetPolicyTree(c.Req.Context(), c.SignedInUser.GetOrgID(), alerting_models.Provenance(provenance)) if err != nil { - return ErrResp(http.StatusInternalServerError, err, "") + return response.ErrOrFallback(http.StatusInternalServerError, "failed to reset notification policy tree", err) } return response.JSON(http.StatusAccepted, tree) } diff --git a/pkg/services/ngalert/api/api_provisioning_test.go b/pkg/services/ngalert/api/api_provisioning_test.go index e6747915ae6..6b418e3b6e4 100644 --- a/pkg/services/ngalert/api/api_provisioning_test.go +++ b/pkg/services/ngalert/api/api_provisioning_test.go @@ -140,7 +140,6 @@ func TestProvisioningApi(t *testing.T) { require.Equal(t, 500, response.Status()) require.NotEmpty(t, response.Body()) - require.Contains(t, string(response.Body()), "something went wrong") }) t.Run("PUT returns 500", func(t *testing.T) { @@ -164,7 +163,6 @@ func TestProvisioningApi(t *testing.T) { require.Equal(t, 500, response.Status()) require.NotEmpty(t, response.Body()) - require.Contains(t, string(response.Body()), "something went wrong") }) }) }) @@ -2002,7 +2000,7 @@ func (f *fakeNotificationPolicyService) UpdatePolicyTree(ctx context.Context, or return nil } -func (f *fakeNotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) { +func (f *fakeNotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64, provenance models.Provenance) (definitions.Route, error) { f.tree = definitions.Route{} // TODO return f.tree, nil } @@ -2017,7 +2015,7 @@ func (f *fakeFailingNotificationPolicyService) UpdatePolicyTree(ctx context.Cont return fmt.Errorf("something went wrong") } -func (f *fakeFailingNotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) { +func (f *fakeFailingNotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64, provenance models.Provenance) (definitions.Route, error) { return definitions.Route{}, fmt.Errorf("something went wrong") } @@ -2031,7 +2029,7 @@ func (f *fakeRejectingNotificationPolicyService) UpdatePolicyTree(ctx context.Co return fmt.Errorf("%w: invalid policy tree", provisioning.ErrValidation) } -func (f *fakeRejectingNotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) { +func (f *fakeRejectingNotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64, provenance models.Provenance) (definitions.Route, error) { return definitions.Route{}, nil } diff --git a/pkg/services/ngalert/api/tooling/definitions/contact_points.go b/pkg/services/ngalert/api/tooling/definitions/contact_points.go index 0cd8f792e4e..4aec132e837 100644 --- a/pkg/services/ngalert/api/tooling/definitions/contact_points.go +++ b/pkg/services/ngalert/api/tooling/definitions/contact_points.go @@ -56,7 +56,7 @@ type EmailIntegration struct { type GooglechatIntegration struct { DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"` - URL string `json:"url" yaml:"url" hcl:"url"` + URL Secret `json:"url" yaml:"url" hcl:"url"` Title *string `json:"title,omitempty" yaml:"title,omitempty" hcl:"title"` Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"` diff --git a/pkg/services/ngalert/notifier/channels_config/available_channels.go b/pkg/services/ngalert/notifier/channels_config/available_channels.go index 4a6433ebb5e..ffe7b757edc 100644 --- a/pkg/services/ngalert/notifier/channels_config/available_channels.go +++ b/pkg/services/ngalert/notifier/channels_config/available_channels.go @@ -1141,6 +1141,7 @@ func GetAvailableNotifiers() []*NotifierPlugin { Placeholder: "Google Chat incoming webhook url", PropertyName: "url", Required: true, + Secure: true, }, { Label: "Title", diff --git a/pkg/services/ngalert/notifier/channels_config/available_channels_test.go b/pkg/services/ngalert/notifier/channels_config/available_channels_test.go index f3290b3956a..2171b2c17fe 100644 --- a/pkg/services/ngalert/notifier/channels_config/available_channels_test.go +++ b/pkg/services/ngalert/notifier/channels_config/available_channels_test.go @@ -26,7 +26,7 @@ func TestGetSecretKeysForContactPointType(t *testing.T) { {receiverType: "wecom", expectedSecretFields: []string{"url", "secret"}}, {receiverType: "prometheus-alertmanager", expectedSecretFields: []string{"basicAuthPassword"}}, {receiverType: "discord", expectedSecretFields: []string{"url"}}, - {receiverType: "googlechat", expectedSecretFields: []string{}}, + {receiverType: "googlechat", expectedSecretFields: []string{"url"}}, {receiverType: "line", expectedSecretFields: []string{"token"}}, {receiverType: "threema", expectedSecretFields: []string{"api_secret"}}, {receiverType: "opsgenie", expectedSecretFields: []string{"apiKey"}}, diff --git a/pkg/services/ngalert/provisioning/notification_policies.go b/pkg/services/ngalert/provisioning/notification_policies.go index 8eeeeff5211..afba5f0c0d9 100644 --- a/pkg/services/ngalert/provisioning/notification_policies.go +++ b/pkg/services/ngalert/provisioning/notification_policies.go @@ -16,6 +16,7 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage" + "github.com/grafana/grafana/pkg/services/ngalert/provisioning/validation" "github.com/grafana/grafana/pkg/setting" ) @@ -25,6 +26,7 @@ type NotificationPolicyService struct { xact TransactionManager log log.Logger settings setting.UnifiedAlertingSettings + validator validation.ProvenanceStatusTransitionValidator } func NewNotificationPolicyService(am alertmanagerConfigStore, prov ProvisioningStore, @@ -35,6 +37,7 @@ func NewNotificationPolicyService(am alertmanagerConfigStore, prov ProvisioningS xact: xact, log: log, settings: settings, + validator: validation.ValidateProvenanceRelaxed, } } @@ -69,11 +72,20 @@ func (nps *NotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgI return err } - err = nps.checkOptimisticConcurrency(*revision.Config.AlertmanagerConfig.Route, models.Provenance(tree.Provenance), version, "update") + err = nps.checkOptimisticConcurrency(*revision.Config.AlertmanagerConfig.Route, p, version, "update") if err != nil { return err } + // check that provenance is not changed in an invalid way + storedProvenance, err := nps.provenanceStore.GetProvenance(ctx, &tree, orgID) + if err != nil { + return err + } + if err := nps.validator(storedProvenance, p); err != nil { + return err + } + receivers, err := nps.receiversToMap(revision.Config.AlertmanagerConfig.Receivers) if err != nil { return err @@ -107,7 +119,15 @@ func (nps *NotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgI }) } -func (nps *NotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) { +func (nps *NotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64, provenance models.Provenance) (definitions.Route, error) { + storedProvenance, err := nps.provenanceStore.GetProvenance(ctx, &definitions.Route{}, orgID) + if err != nil { + return definitions.Route{}, err + } + if err := nps.validator(storedProvenance, provenance); err != nil { + return definitions.Route{}, err + } + defaultCfg, err := legacy_storage.DeserializeAlertmanagerConfig([]byte(nps.settings.DefaultConfiguration)) if err != nil { nps.log.Error("Failed to parse default alertmanager config: %w", err) diff --git a/pkg/services/ngalert/provisioning/notification_policies_test.go b/pkg/services/ngalert/provisioning/notification_policies_test.go index e67fb83be32..9fa49c8f5f1 100644 --- a/pkg/services/ngalert/provisioning/notification_policies_test.go +++ b/pkg/services/ngalert/provisioning/notification_policies_test.go @@ -2,6 +2,7 @@ package provisioning import ( "context" + "errors" "testing" "github.com/grafana/alerting/definition" @@ -102,7 +103,10 @@ func TestUpdatePolicyTree(t *testing.T) { t.Run("ErrValidation if referenced receiver does not exist", func(t *testing.T) { rev := getDefaultConfigRevision() - sut, store, _ := createNotificationPolicyServiceSut() + sut, store, prov := createNotificationPolicyServiceSut() + prov.GetProvenanceFunc = func(ctx context.Context, o models.Provisionable, org int64) (models.Provenance, error) { + return models.ProvenanceNone, nil + } store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { return &rev, nil } @@ -137,6 +141,35 @@ func TestUpdatePolicyTree(t *testing.T) { require.ErrorIs(t, err, ErrVersionConflict) }) + t.Run("Error if provenance validation fails", func(t *testing.T) { + sut, store, prov := createNotificationPolicyServiceSut() + prov.GetProvenanceFunc = func(ctx context.Context, o models.Provisionable, org int64) (models.Provenance, error) { + return models.ProvenanceAPI, nil + } + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return &rev, nil + } + expectedRev := getDefaultConfigRevision() + route := newRoute + expectedRev.ConcurrencyToken = rev.ConcurrencyToken + expectedRev.Config.AlertmanagerConfig.Route = &route + + expectedErr := errors.New("test") + sut.validator = func(from, to models.Provenance) error { + assert.Equal(t, models.ProvenanceAPI, from) + assert.Equal(t, models.ProvenanceNone, to) + return expectedErr + } + + err := sut.UpdatePolicyTree(context.Background(), orgID, newRoute, models.ProvenanceNone, defaultVersion) + require.ErrorIs(t, err, expectedErr) + + assert.Len(t, prov.Calls, 1) + assert.Equal(t, "GetProvenance", prov.Calls[0].MethodName) + assert.IsType(t, &definitions.Route{}, prov.Calls[0].Arguments[1]) + assert.Equal(t, orgID, prov.Calls[0].Arguments[2].(int64)) + }) + t.Run("updates Route and sets provenance in transaction if route is valid and version matches", func(t *testing.T) { sut, store, prov := createNotificationPolicyServiceSut() store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { @@ -155,12 +188,16 @@ func TestUpdatePolicyTree(t *testing.T) { assertInTransaction(t, store.Calls[1].Args[0].(context.Context)) assert.Equal(t, &expectedRev, store.Calls[1].Args[1]) - assert.Len(t, prov.Calls, 1) - assert.Equal(t, "SetProvenance", prov.Calls[0].MethodName) - assertInTransaction(t, prov.Calls[0].Arguments[0].(context.Context)) - assert.IsType(t, &definitions.Route{}, prov.Calls[0].Arguments[1]) - assert.Equal(t, orgID, prov.Calls[0].Arguments[2].(int64)) - assert.Equal(t, models.ProvenanceAPI, prov.Calls[0].Arguments[3].(models.Provenance)) + c := prov.Calls[0] + assert.Equal(t, "GetProvenance", c.MethodName) + assert.IsType(t, &definitions.Route{}, c.Arguments[1]) + assert.Equal(t, orgID, c.Arguments[2].(int64)) + c = prov.Calls[1] + assert.Equal(t, "SetProvenance", c.MethodName) + assertInTransaction(t, c.Arguments[0].(context.Context)) + assert.IsType(t, &definitions.Route{}, c.Arguments[1]) + assert.Equal(t, orgID, c.Arguments[2].(int64)) + assert.Equal(t, models.ProvenanceAPI, c.Arguments[3].(models.Provenance)) }) t.Run("bypasses optimistic concurrency if provided version is empty", func(t *testing.T) { @@ -181,12 +218,13 @@ func TestUpdatePolicyTree(t *testing.T) { assertInTransaction(t, store.Calls[1].Args[0].(context.Context)) assert.Equal(t, &expectedRev, store.Calls[1].Args[1]) - assert.Len(t, prov.Calls, 1) - assert.Equal(t, "SetProvenance", prov.Calls[0].MethodName) - assertInTransaction(t, prov.Calls[0].Arguments[0].(context.Context)) - assert.IsType(t, &definitions.Route{}, prov.Calls[0].Arguments[1]) - assert.Equal(t, orgID, prov.Calls[0].Arguments[2].(int64)) - assert.Equal(t, models.ProvenanceAPI, prov.Calls[0].Arguments[3].(models.Provenance)) + assert.Len(t, prov.Calls, 2) + c := prov.Calls[1] + assert.Equal(t, "SetProvenance", c.MethodName) + assertInTransaction(t, c.Arguments[0].(context.Context)) + assert.IsType(t, &definitions.Route{}, c.Arguments[1]) + assert.Equal(t, orgID, c.Arguments[2].(int64)) + assert.Equal(t, models.ProvenanceAPI, c.Arguments[3].(models.Provenance)) }) } @@ -223,10 +261,27 @@ func TestResetPolicyTree(t *testing.T) { sut.settings = setting.UnifiedAlertingSettings{ DefaultConfiguration: "{", } - _, err := sut.ResetPolicyTree(context.Background(), orgID) + _, err := sut.ResetPolicyTree(context.Background(), orgID, models.ProvenanceNone) require.ErrorContains(t, err, "failed to parse default alertmanager config") }) + t.Run("Error if provenance validation fails", func(t *testing.T) { + sut, _, prov := createNotificationPolicyServiceSut() + prov.GetProvenanceFunc = func(ctx context.Context, o models.Provisionable, org int64) (models.Provenance, error) { + return models.ProvenanceAPI, nil + } + + expectedErr := errors.New("test") + sut.validator = func(from, to models.Provenance) error { + assert.Equal(t, models.ProvenanceAPI, from) + assert.Equal(t, models.ProvenanceNone, to) + return expectedErr + } + + _, err := sut.ResetPolicyTree(context.Background(), orgID, models.ProvenanceNone) + require.ErrorIs(t, err, expectedErr) + }) + t.Run("replaces route with one from the default config and copies receivers if do not exist", func(t *testing.T) { defaultConfig := getDefaultConfigRevision().Config data, err := legacy_storage.SerializeAlertmanagerConfig(*defaultConfig) @@ -252,7 +307,7 @@ func TestResetPolicyTree(t *testing.T) { expectedRev.Config.AlertmanagerConfig.Route = getDefaultConfigRevision().Config.AlertmanagerConfig.Route expectedRev.Config.AlertmanagerConfig.Receivers = append(expectedRev.Config.AlertmanagerConfig.Receivers, getDefaultConfigRevision().Config.AlertmanagerConfig.Receivers[0]) - tree, err := sut.ResetPolicyTree(context.Background(), orgID) + tree, err := sut.ResetPolicyTree(context.Background(), orgID, models.ProvenanceNone) require.NoError(t, err) assert.Equal(t, *defaultConfig.AlertmanagerConfig.Route, tree) @@ -262,11 +317,16 @@ func TestResetPolicyTree(t *testing.T) { resetRev := store.Calls[1].Args[1].(*legacy_storage.ConfigRevision) assert.Equal(t, expectedRev.Config.AlertmanagerConfig, resetRev.Config.AlertmanagerConfig) - assert.Len(t, prov.Calls, 1) - assert.Equal(t, "DeleteProvenance", prov.Calls[0].MethodName) - assertInTransaction(t, prov.Calls[0].Arguments[0].(context.Context)) - assert.IsType(t, &definitions.Route{}, prov.Calls[0].Arguments[1]) - assert.Equal(t, orgID, prov.Calls[0].Arguments[2]) + assert.Len(t, prov.Calls, 2) + c := prov.Calls[0] + assert.Equal(t, "GetProvenance", c.MethodName) + assert.IsType(t, &definitions.Route{}, c.Arguments[1]) + assert.Equal(t, orgID, c.Arguments[2].(int64)) + c = prov.Calls[1] + assert.Equal(t, "DeleteProvenance", c.MethodName) + assertInTransaction(t, c.Arguments[0].(context.Context)) + assert.IsType(t, &definitions.Route{}, c.Arguments[1]) + assert.Equal(t, orgID, c.Arguments[2]) }) } @@ -286,6 +346,9 @@ func createNotificationPolicyServiceSut() (*NotificationPolicyService, *legacy_s settings: setting.UnifiedAlertingSettings{ DefaultConfiguration: setting.GetAlertmanagerDefaultConfiguration(), }, + validator: func(from, to models.Provenance) error { + return nil + }, }, configStore, prov } diff --git a/pkg/services/pluginsintegration/loader/loader_test.go b/pkg/services/pluginsintegration/loader/loader_test.go index 4eafd7db235..677b07bcb27 100644 --- a/pkg/services/pluginsintegration/loader/loader_test.go +++ b/pkg/services/pluginsintegration/loader/loader_test.go @@ -10,6 +10,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/plugins" @@ -584,7 +585,7 @@ func TestLoader_Load_ExternalRegistration(t *testing.T) { backendFactoryProvider := fakes.NewFakeBackendProcessProvider() backendFactoryProvider.BackendFactoryFunc = func(ctx context.Context, plugin *plugins.Plugin) backendplugin.PluginFactoryFunc { - return func(pluginID string, logger log.Logger, env func() []string) (backendplugin.Plugin, error) { + return func(pluginID string, logger log.Logger, tracer trace.Tracer, env func() []string) (backendplugin.Plugin, error) { require.Equal(t, "grafana-test-datasource", pluginID) return &fakes.FakeBackendPlugin{}, nil } @@ -1133,7 +1134,7 @@ func TestLoader_Load_SkipUninitializedPlugins(t *testing.T) { procPrvdr := fakes.NewFakeBackendProcessProvider() // Cause an initialization error procPrvdr.BackendFactoryFunc = func(ctx context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc { - return func(pluginID string, _ log.Logger, _ func() []string) (backendplugin.Plugin, error) { + return func(pluginID string, _ log.Logger, _ trace.Tracer, _ func() []string) (backendplugin.Plugin, error) { if pluginID == "test-datasource" { return nil, errors.New("failed to initialize") } diff --git a/pkg/services/pluginsintegration/pipeline/pipeline.go b/pkg/services/pluginsintegration/pipeline/pipeline.go index 6422c7ddd61..a590d8c810a 100644 --- a/pkg/services/pluginsintegration/pipeline/pipeline.go +++ b/pkg/services/pluginsintegration/pipeline/pipeline.go @@ -68,7 +68,7 @@ func ProvideInitializationStage(cfg *config.PluginManagementCfg, pr registry.Ser return initialization.New(cfg, initialization.Opts{ InitializeFuncs: []initialization.InitializeFunc{ ExternalServiceRegistrationStep(cfg, externalServiceRegistry, tracer), - initialization.BackendClientInitStep(pluginEnvProvider, bp), + initialization.BackendClientInitStep(pluginEnvProvider, bp, tracer), initialization.BackendProcessStartStep(pm), RegisterPluginRolesStep(roleRegistry), RegisterActionSetsStep(actionSetRegistry), diff --git a/pkg/services/pluginsintegration/renderer/renderer.go b/pkg/services/pluginsintegration/renderer/renderer.go index 9eb71fc7d7d..ff8003b957c 100644 --- a/pkg/services/pluginsintegration/renderer/renderer.go +++ b/pkg/services/pluginsintegration/renderer/renderer.go @@ -5,6 +5,7 @@ import ( "errors" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2" "github.com/grafana/grafana/pkg/plugins/backendplugin/provider" @@ -22,11 +23,12 @@ import ( "github.com/grafana/grafana/pkg/services/pluginsintegration/pipeline" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginerrs" "github.com/grafana/grafana/pkg/services/rendering" + "go.opentelemetry.io/otel/trace" ) func ProvideService(cfg *config.PluginManagementCfg, pluginEnvProvider envvars.Provider, - registry registry.Service) (*Manager, error) { - l, err := createLoader(cfg, pluginEnvProvider, registry) + registry registry.Service, tracer tracing.Tracer) (*Manager, error) { + l, err := createLoader(cfg, pluginEnvProvider, registry, tracer) if err != nil { return nil, err } @@ -105,7 +107,7 @@ func (m *Manager) Renderer(ctx context.Context) (rendering.Plugin, bool) { } func createLoader(cfg *config.PluginManagementCfg, pluginEnvProvider envvars.Provider, - pr registry.Service) (loader.Service, error) { + pr registry.Service, tracer trace.Tracer) (loader.Service, error) { d := discovery.New(cfg, discovery.Opts{ FindFilterFuncs: []discovery.FindFilterFunc{ discovery.NewPermittedPluginTypesFilterStep([]plugins.Type{plugins.TypeRenderer}), @@ -124,7 +126,7 @@ func createLoader(cfg *config.PluginManagementCfg, pluginEnvProvider envvars.Pro }) i := initialization.New(cfg, initialization.Opts{ InitializeFuncs: []initialization.InitializeFunc{ - initialization.BackendClientInitStep(pluginEnvProvider, provider.New(provider.RendererProvider)), + initialization.BackendClientInitStep(pluginEnvProvider, provider.New(provider.RendererProvider), tracer), initialization.PluginRegistrationStep(pr), }, }) diff --git a/pkg/services/provisioning/alerting/notification_policy_provisioner.go b/pkg/services/provisioning/alerting/notification_policy_provisioner.go index 55f3dd2ae9d..63f6cbe9ce3 100644 --- a/pkg/services/provisioning/alerting/notification_policy_provisioner.go +++ b/pkg/services/provisioning/alerting/notification_policy_provisioner.go @@ -45,7 +45,7 @@ func (c *defaultNotificationPolicyProvisioner) Unprovision(ctx context.Context, files []*AlertingFile) error { for _, file := range files { for _, orgID := range file.ResetPolicies { - _, err := c.notificationPolicyService.ResetPolicyTree(ctx, int64(orgID)) + _, err := c.notificationPolicyService.ResetPolicyTree(ctx, int64(orgID), models.ProvenanceFile) if err != nil { return fmt.Errorf("%s: %w", file.Filename, err) } diff --git a/pkg/services/sqlstore/migrations/user_mig.go b/pkg/services/sqlstore/migrations/user_mig.go index d4d8384a4d3..68fe8ba9af4 100644 --- a/pkg/services/sqlstore/migrations/user_mig.go +++ b/pkg/services/sqlstore/migrations/user_mig.go @@ -158,6 +158,9 @@ func addUserMigrations(mg *Migrator) { // Service accounts login were not unique per org. this migration is part of making it unique per org // to be able to create service accounts that are unique per org mg.AddMigration(usermig.AllowSameLoginCrossOrgs, &usermig.ServiceAccountsSameLoginCrossOrgs{}) + // Before it was fixed, the previous migration introduced the org_id again in logins that already had it. + // This migration removes the duplicate org_id from the login. + mg.AddMigration(usermig.DedupOrgInLogin, &usermig.ServiceAccountsDeduplicateOrgInLogin{}) // Users login and email should be in lower case mg.AddMigration(usermig.LowerCaseUserLoginAndEmail, &usermig.UsersLowerCaseLoginAndEmail{}) diff --git a/pkg/services/sqlstore/migrations/usermig/service_account_multiple_org_login_migrator.go b/pkg/services/sqlstore/migrations/usermig/service_account_multiple_org_login_migrator.go index 0737091bda4..54b71250f1b 100644 --- a/pkg/services/sqlstore/migrations/usermig/service_account_multiple_org_login_migrator.go +++ b/pkg/services/sqlstore/migrations/usermig/service_account_multiple_org_login_migrator.go @@ -16,9 +16,6 @@ const ( // to be able to create service accounts that are unique per org func AddServiceAccountsAllowSameLoginCrossOrgs(mg *migrator.Migrator) { mg.AddMigration(AllowSameLoginCrossOrgs, &ServiceAccountsSameLoginCrossOrgs{}) - // Before it was fixed, the previous migration introduced the org_id again in logins that already had it. - // This migration removes the duplicate org_id from the login. - mg.AddMigration(DedupOrgInLogin, &ServiceAccountsDeduplicateOrgInLogin{}) } var _ migrator.CodeMigration = new(ServiceAccountsSameLoginCrossOrgs) diff --git a/pkg/services/ssosettings/strategies/saml_strategy.go b/pkg/services/ssosettings/strategies/saml_strategy.go index e6e820474fc..740d86ee2c0 100644 --- a/pkg/services/ssosettings/strategies/saml_strategy.go +++ b/pkg/services/ssosettings/strategies/saml_strategy.go @@ -33,6 +33,7 @@ func (s *SAMLStrategy) loadSAMLSettings() map[string]any { section := s.settingsProvider.Section("auth.saml") result := map[string]any{ "enabled": section.KeyValue("enabled").MustBool(false), + "name": section.KeyValue("name").MustString("SAML"), "single_logout": section.KeyValue("single_logout").MustBool(false), "allow_sign_up": section.KeyValue("allow_sign_up").MustBool(false), "auto_login": section.KeyValue("auto_login").MustBool(false), diff --git a/pkg/services/ssosettings/strategies/saml_strategy_test.go b/pkg/services/ssosettings/strategies/saml_strategy_test.go index fb61b54893e..d076f708400 100644 --- a/pkg/services/ssosettings/strategies/saml_strategy_test.go +++ b/pkg/services/ssosettings/strategies/saml_strategy_test.go @@ -16,8 +16,9 @@ var ( [auth.saml] enabled = true single_logout = true + name = "SAML Test" allow_sign_up = true - auto_login = false + auto_login = true certificate = devenv/docker/blocks/auth/saml-enterprise/cert.crt certificate_path = /path/to/cert private_key = dGhpcyBpcyBteSBwcml2YXRlIGtleSB0aGF0IEkgd2FudCB0byBnZXQgZW5jb2RlZCBpbiBiYXNlIDY0 @@ -55,7 +56,8 @@ var ( "enabled": true, "single_logout": true, "allow_sign_up": true, - "auto_login": false, + "auto_login": true, + "name": "SAML Test", "certificate": "devenv/docker/blocks/auth/saml-enterprise/cert.crt", "certificate_path": "/path/to/cert", "private_key": "dGhpcyBpcyBteSBwcml2YXRlIGtleSB0aGF0IEkgd2FudCB0byBnZXQgZW5jb2RlZCBpbiBiYXNlIDY0", diff --git a/pkg/services/unifiedSearch/service.go b/pkg/services/unifiedSearch/service.go index eb83ac8bbaa..7db7a0bffb5 100644 --- a/pkg/services/unifiedSearch/service.go +++ b/pkg/services/unifiedSearch/service.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/org" @@ -151,16 +152,20 @@ func (s *StandardSearchService) DoQuery(ctx context.Context, user *backend.User, } func (s *StandardSearchService) doQuery(ctx context.Context, signedInUser *user.SignedInUser, orgID int64, q Query) *backend.DataResponse { - response := s.doSearchQuery(ctx, q, s.cfg.AppSubURL) + response := s.doSearchQuery(ctx, q, s.cfg.AppSubURL, orgID) return response } -func (s *StandardSearchService) doSearchQuery(ctx context.Context, qry Query, _ string) *backend.DataResponse { +func (s *StandardSearchService) doSearchQuery(ctx context.Context, qry Query, _ string, orgID int64) *backend.DataResponse { response := &backend.DataResponse{} - req := &resource.SearchRequest{Tenant: s.cfg.StackID, Query: qry.Query} + // will use stack id for cloud and org id for on-prem + tenantId := request.GetNamespaceMapper(s.cfg)(orgID) + + req := &resource.SearchRequest{Tenant: tenantId, Query: qry.Query, Limit: int64(qry.Limit), Offset: int64(qry.From)} res, err := s.resourceClient.Search(ctx, req) if err != nil { + s.logger.Error("Failed to search resources", "error", err) response.Error = err return response } diff --git a/pkg/storage/unified/resource/go.mod b/pkg/storage/unified/resource/go.mod index 3be85811afb..7f5f354e248 100644 --- a/pkg/storage/unified/resource/go.mod +++ b/pkg/storage/unified/resource/go.mod @@ -43,6 +43,32 @@ require ( go.etcd.io/bbolt v1.3.9 // indirect ) +require ( + github.com/RoaringBitmap/roaring v1.9.3 // indirect + github.com/bits-and-blooms/bitset v1.12.0 // indirect + github.com/blevesearch/bleve_index_api v1.1.10 // indirect + github.com/blevesearch/geo v0.1.20 // indirect + github.com/blevesearch/go-faiss v1.0.20 // indirect + github.com/blevesearch/go-porterstemmer v1.0.3 // indirect + github.com/blevesearch/gtreap v0.1.1 // indirect + github.com/blevesearch/mmap-go v1.0.4 // indirect + github.com/blevesearch/scorch_segment_api/v2 v2.2.15 // indirect + github.com/blevesearch/segment v0.9.1 // indirect + github.com/blevesearch/snowballstem v0.9.0 // indirect + github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect + github.com/blevesearch/vellum v1.0.10 // indirect + github.com/blevesearch/zapx/v11 v11.3.10 // indirect + github.com/blevesearch/zapx/v12 v12.3.10 // indirect + github.com/blevesearch/zapx/v13 v13.3.10 // indirect + github.com/blevesearch/zapx/v14 v14.3.10 // indirect + github.com/blevesearch/zapx/v15 v15.3.13 // indirect + github.com/blevesearch/zapx/v16 v16.1.5 // indirect + github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/mschoch/smat v0.2.0 // indirect + go.etcd.io/bbolt v1.3.9 // indirect +) + require ( github.com/beorn7/perks v1.0.1 // indirect github.com/blevesearch/bleve/v2 v2.4.2 diff --git a/pkg/storage/unified/resource/index.go b/pkg/storage/unified/resource/index.go index cdd8b6f68c5..33abc5e92c7 100644 --- a/pkg/storage/unified/resource/index.go +++ b/pkg/storage/unified/resource/index.go @@ -4,13 +4,16 @@ import ( "context" "encoding/json" "fmt" - "log" + golog "log" "os" + "strings" "github.com/blevesearch/bleve/v2" "github.com/blevesearch/bleve/v2/analysis/lang/en" "github.com/blevesearch/bleve/v2/mapping" "github.com/google/uuid" + "github.com/grafana/grafana/pkg/infra/log" + "golang.org/x/exp/slices" ) type Shard struct { @@ -23,6 +26,7 @@ type Index struct { shards map[string]Shard opts Opts s *server + log log.Logger } func NewIndex(s *server, opts Opts) *Index { @@ -30,47 +34,70 @@ func NewIndex(s *server, opts Opts) *Index { s: s, opts: opts, shards: make(map[string]Shard), + log: log.New("unifiedstorage.search.index"), } return idx } +func (i *Index) IndexBatch(list *ListResponse, kind string) error { + for _, obj := range list.Items { + res, err := getResource(obj.Value) + if err != nil { + return err + } + + shard, err := i.getShard(tenant(res)) + if err != nil { + return err + } + i.log.Debug("initial indexing resources batch", "count", len(list.Items), "kind", kind, "tenant", tenant(res)) + + var jsonDoc interface{} + err = json.Unmarshal(obj.Value, &jsonDoc) + if err != nil { + return err + } + err = shard.batch.Index(res.Metadata.Uid, jsonDoc) + if err != nil { + return err + } + } + + for _, shard := range i.shards { + err := shard.index.Batch(shard.batch) + if err != nil { + return err + } + shard.batch.Reset() + } + + return nil +} + func (i *Index) Init(ctx context.Context) error { resourceTypes := fetchResourceTypes() for _, rt := range resourceTypes { - r := &ListRequest{Options: rt} - list, err := i.s.List(ctx, r) - if err != nil { - return err - } + i.log.Info("indexing resource", "kind", rt.Key.Resource) + r := &ListRequest{Options: rt, Limit: 100} - for _, obj := range list.Items { - res, err := getResource(obj.Value) + // Paginate through the list of resources and index each page + for { + list, err := i.s.List(ctx, r) if err != nil { return err } - shard, err := i.getShard(tenant(res)) + // Index current page + err = i.IndexBatch(list, rt.Key.Resource) if err != nil { return err } - var jsonDoc interface{} - err = json.Unmarshal(obj.Value, &jsonDoc) - if err != nil { - return err + if list.NextPageToken == "" { + break } - err = shard.batch.Index(res.Metadata.Uid, jsonDoc) - if err != nil { - return err - } - } - for _, shard := range i.shards { - err := shard.index.Batch(shard.batch) - if err != nil { - return err - } - shard.batch.Reset() + r.NextPageToken = list.NextPageToken } } @@ -83,6 +110,7 @@ func (i *Index) Index(ctx context.Context, data *Data) error { return err } tenant := tenant(res) + i.log.Debug("indexing resource for tenant", "res", res, "tenant", tenant) shard, err := i.getShard(tenant) if err != nil { return err @@ -111,7 +139,7 @@ func (i *Index) Delete(ctx context.Context, uid string, key *ResourceKey) error return nil } -func (i *Index) Search(ctx context.Context, tenant string, query string) ([]string, error) { +func (i *Index) Search(ctx context.Context, tenant string, query string, limit int, offset int) ([]SearchSummary, error) { if tenant == "" { tenant = "default" } @@ -119,20 +147,58 @@ func (i *Index) Search(ctx context.Context, tenant string, query string) ([]stri if err != nil { return nil, err } - req := bleve.NewSearchRequest(bleve.NewQueryStringQuery(query)) - req.Fields = []string{"kind", "spec.title"} + docCount, err := shard.index.DocCount() + if err != nil { + return nil, err + } + i.log.Info("got index for tenant", "tenant", tenant, "docCount", docCount) + // use 10 as a default limit for now + if limit <= 0 { + limit = 10 + } + + req := bleve.NewSearchRequest(bleve.NewQueryStringQuery(query)) + req.From = offset + req.Size = limit + + req.Fields = []string{"*"} // return all indexed fields in search results + + i.log.Info("searching index", "query", query, "tenant", tenant) res, err := shard.index.Search(req) if err != nil { return nil, err } - hits := res.Hits - results := []string{} - for _, hit := range hits { - val := fmt.Sprintf("%s:%s", hit.Fields["kind"], hit.Fields["spec.title"]) - results = append(results, val) + + i.log.Info("got search results", "hits", hits) + + results := make([]SearchSummary, len(hits)) + for resKey, hit := range hits { + searchSummary := SearchSummary{} + + // add common fields to search results + searchSummary.Kind = hit.Fields["kind"].(string) + searchSummary.Metadata.CreationTimestamp = hit.Fields["metadata.creationTimestamp"].(string) + searchSummary.Metadata.Uid = hit.Fields["metadata.uid"].(string) + + // add allowed indexed spec fields to search results + specResult := map[string]interface{}{} + for k, v := range hit.Fields { + if strings.HasPrefix(k, "spec.") { + mappedFields := specFieldMappings(searchSummary.Kind) + // should only include spec fields we care about in search results + if slices.Contains(mappedFields, k) { + specKey := strings.TrimPrefix(k, "spec.") + specResult[specKey] = v + } + } + searchSummary.Spec = specResult + } + + results[resKey] = searchSummary } + return results, nil } @@ -140,11 +206,17 @@ func tenant(res *Resource) string { return res.Metadata.Namespace } +type SearchSummary struct { + Kind string `json:"kind"` + Metadata `json:"metadata"` + Spec map[string]interface{} `json:"spec"` +} + type Metadata struct { Name string Namespace string - Uid string - CreationTimestamp string + Uid string `json:"uid"` + CreationTimestamp string `json:"creationTimestamp"` Labels map[string]string Annotations map[string]string } @@ -165,32 +237,31 @@ func createFileIndex() (bleve.Index, string, error) { indexPath := fmt.Sprintf("%s%s.bleve", os.TempDir(), uuid.New().String()) index, err := bleve.New(indexPath, createIndexMappings()) if err != nil { - log.Fatalf("Failed to create index: %v", err) + golog.Fatalf("Failed to create index: %v", err) } return index, indexPath, err } -// TODO: clean this up. it was copied from owens performance test func createIndexMappings() *mapping.IndexMappingImpl { - //Create mapping for the name and creationTimestamp fields in the metadata - nameFieldMapping := bleve.NewTextFieldMapping() + //Create mapping for the creationTimestamp field in the metadata creationTimestampFieldMapping := bleve.NewDateTimeFieldMapping() + uidMapping := bleve.NewTextFieldMapping() metaMapping := bleve.NewDocumentMapping() - metaMapping.AddFieldMappingsAt("name", nameFieldMapping) metaMapping.AddFieldMappingsAt("creationTimestamp", creationTimestampFieldMapping) + metaMapping.AddFieldMappingsAt("uid", uidMapping) metaMapping.Dynamic = false metaMapping.Enabled = true + // Spec is different for all resources, so we create a dynamic mapping for it to index all fields (for now) specMapping := bleve.NewDocumentMapping() - specMapping.AddFieldMappingsAt("title", nameFieldMapping) - specMapping.Dynamic = false + specMapping.Dynamic = true specMapping.Enabled = true //Create a sub-document mapping for the metadata field objectMapping := bleve.NewDocumentMapping() objectMapping.AddSubDocumentMapping("metadata", metaMapping) objectMapping.AddSubDocumentMapping("spec", specMapping) - objectMapping.Dynamic = false + objectMapping.Dynamic = true objectMapping.Enabled = true // a generic reusable mapping for english text @@ -248,3 +319,14 @@ func fetchResourceTypes() []*ListOptions { }) return items } + +func specFieldMappings(kind string) []string { + mappedFields := map[string][]string{ + "Playlist": { + "spec.title", + "spec.interval", + }, + } + + return mappedFields[kind] +} diff --git a/pkg/storage/unified/resource/index_server.go b/pkg/storage/unified/resource/index_server.go index 47937e7f7f3..8feedacd60e 100644 --- a/pkg/storage/unified/resource/index_server.go +++ b/pkg/storage/unified/resource/index_server.go @@ -2,6 +2,7 @@ package resource import ( "context" + "encoding/json" "errors" "log" "strings" @@ -16,23 +17,27 @@ type IndexServer struct { ws *indexWatchServer } -func (is IndexServer) Search(ctx context.Context, req *SearchRequest) (*SearchResponse, error) { - results, err := is.index.Search(ctx, req.Tenant, req.Query) +func (is *IndexServer) Search(ctx context.Context, req *SearchRequest) (*SearchResponse, error) { + results, err := is.index.Search(ctx, req.Tenant, req.Query, int(req.Limit), int(req.Offset)) if err != nil { return nil, err } res := &SearchResponse{} for _, r := range results { - res.Items = append(res.Items, &ResourceWrapper{Value: []byte(r)}) + resJsonBytes, err := json.Marshal(r) + if err != nil { + return nil, err + } + res.Items = append(res.Items, &ResourceWrapper{Value: resJsonBytes}) } return res, nil } -func (is IndexServer) History(ctx context.Context, req *HistoryRequest) (*HistoryResponse, error) { +func (is *IndexServer) History(ctx context.Context, req *HistoryRequest) (*HistoryResponse, error) { return nil, nil } -func (is IndexServer) Origin(ctx context.Context, req *OriginRequest) (*OriginResponse, error) { +func (is *IndexServer) Origin(ctx context.Context, req *OriginRequest) (*OriginResponse, error) { return nil, nil } diff --git a/pkg/storage/unified/resource/resource.pb.go b/pkg/storage/unified/resource/resource.pb.go index 7728dd880be..1a641f12284 100644 --- a/pkg/storage/unified/resource/resource.pb.go +++ b/pkg/storage/unified/resource/resource.pb.go @@ -1619,8 +1619,16 @@ type SearchRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"` - Tenant string `protobuf:"bytes,2,opt,name=tenant,proto3" json:"tenant,omitempty"` + // query string for chosen implementation (currently just bleve) + Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"` + // default to bleve + QueryType string `protobuf:"bytes,2,opt,name=queryType,proto3" json:"queryType,omitempty"` + Tenant string `protobuf:"bytes,3,opt,name=tenant,proto3" json:"tenant,omitempty"` + // resource kind (playlists, dashboards, etc) + Kind string `protobuf:"bytes,4,opt,name=kind,proto3" json:"kind,omitempty"` + // pagination support + Limit int64 `protobuf:"varint,5,opt,name=limit,proto3" json:"limit,omitempty"` + Offset int64 `protobuf:"varint,6,opt,name=offset,proto3" json:"offset,omitempty"` } func (x *SearchRequest) Reset() { @@ -1662,6 +1670,13 @@ func (x *SearchRequest) GetQuery() string { return "" } +func (x *SearchRequest) GetQueryType() string { + if x != nil { + return x.QueryType + } + return "" +} + func (x *SearchRequest) GetTenant() string { if x != nil { return x.Tenant @@ -1669,6 +1684,27 @@ func (x *SearchRequest) GetTenant() string { return "" } +func (x *SearchRequest) GetKind() string { + if x != nil { + return x.Kind + } + return "" +} + +func (x *SearchRequest) GetLimit() int64 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *SearchRequest) GetOffset() int64 { + if x != nil { + return x.Offset + } + return 0 +} + type SearchResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2449,136 +2485,142 @@ var file_resource_proto_rawDesc = []byte{ 0x09, 0x0a, 0x05, 0x41, 0x44, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x4d, 0x4f, 0x44, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x42, 0x4f, 0x4f, 0x4b, 0x4d, 0x41, 0x52, - 0x4b, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x22, 0x3d, - 0x0a, 0x0d, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x22, 0x41, 0x0a, - 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x2f, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, - 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, - 0x22, 0x9a, 0x01, 0x0a, 0x0e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, - 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, - 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, - 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, - 0x74, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, - 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x68, - 0x6f, 0x77, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0b, 0x73, 0x68, 0x6f, 0x77, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0xbf, 0x01, - 0x0a, 0x0f, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x2c, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, - 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, - 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, - 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, - 0x8e, 0x01, 0x0a, 0x0d, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, - 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, - 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, - 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x72, 0x69, 0x67, - 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, - 0x22, 0xe5, 0x01, 0x0a, 0x12, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x72, 0x69, - 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x73, 0x69, 0x7a, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x72, - 0x69, 0x67, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x72, 0x69, 0x67, - 0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xc4, 0x01, 0x0a, 0x0e, 0x4f, 0x72, 0x69, - 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x05, 0x69, - 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x72, - 0x69, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, - 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, - 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, - 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, - 0x2e, 0x0a, 0x12, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, - 0xab, 0x01, 0x0a, 0x13, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x4f, 0x0a, 0x0d, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, - 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x45, - 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4e, 0x4f, 0x54, 0x5f, 0x53, - 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x45, 0x52, 0x56, - 0x49, 0x43, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x03, 0x2a, 0x33, 0x0a, - 0x14, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x10, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, 0x4f, 0x6c, 0x64, 0x65, - 0x72, 0x54, 0x68, 0x61, 0x6e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x78, 0x61, 0x63, 0x74, - 0x10, 0x01, 0x32, 0xed, 0x02, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, - 0x74, 0x6f, 0x72, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, 0x12, 0x15, 0x2e, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, - 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, - 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, - 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x15, 0x2e, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x57, 0x61, 0x74, - 0x63, 0x68, 0x12, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, - 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x30, 0x01, 0x32, 0xc9, 0x01, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, - 0x6e, 0x64, 0x65, 0x78, 0x12, 0x3b, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x17, - 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x18, 0x2e, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x12, 0x17, 0x2e, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, - 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x57, - 0x0a, 0x0b, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x73, 0x12, 0x48, 0x0a, - 0x09, 0x49, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x1c, 0x2e, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, - 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x4b, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x22, 0x9d, + 0x01, 0x0a, 0x0d, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x71, 0x75, 0x65, 0x72, 0x79, 0x54, + 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x71, 0x75, 0x65, 0x72, 0x79, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, + 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, + 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, 0x41, + 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x2f, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, + 0x73, 0x22, 0x9a, 0x01, 0x0a, 0x0e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, + 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, + 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, + 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, + 0x69, 0x74, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x73, + 0x68, 0x6f, 0x77, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0b, 0x73, 0x68, 0x6f, 0x77, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0xbf, + 0x01, 0x0a, 0x0f, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, + 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, + 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, + 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x22, 0x8e, 0x01, 0x0a, 0x0d, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, + 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, + 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x72, 0x69, + 0x67, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, + 0x6e, 0x22, 0xe5, 0x01, 0x0a, 0x12, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x72, + 0x69, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x73, 0x69, + 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x6f, + 0x72, 0x69, 0x67, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x72, 0x69, + 0x67, 0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1c, 0x0a, 0x09, 0x74, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xc4, 0x01, 0x0a, 0x0e, 0x4f, 0x72, + 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x05, + 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, + 0x72, 0x69, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, + 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, + 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, + 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x22, 0x2e, 0x0a, 0x12, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x22, 0xab, 0x01, 0x0a, 0x13, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x67, 0x72, - 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, - 0x65, 0x2f, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x4f, 0x0a, + 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, + 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, + 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4e, 0x4f, 0x54, 0x5f, + 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x45, 0x52, + 0x56, 0x49, 0x43, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x03, 0x2a, 0x33, + 0x0a, 0x14, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x10, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, 0x4f, 0x6c, 0x64, + 0x65, 0x72, 0x54, 0x68, 0x61, 0x6e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x78, 0x61, 0x63, + 0x74, 0x10, 0x01, 0x32, 0xed, 0x02, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, 0x12, 0x15, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, + 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x15, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x57, 0x61, + 0x74, 0x63, 0x68, 0x12, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, + 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x30, 0x01, 0x32, 0xc9, 0x01, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x3b, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, + 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, + 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x18, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x12, 0x17, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, + 0x57, 0x0a, 0x0b, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x73, 0x12, 0x48, + 0x0a, 0x09, 0x49, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x1c, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x67, + 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, + 0x67, 0x65, 0x2f, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/pkg/storage/unified/resource/resource.proto b/pkg/storage/unified/resource/resource.proto index dcaee25b909..bfbb654a3ae 100644 --- a/pkg/storage/unified/resource/resource.proto +++ b/pkg/storage/unified/resource/resource.proto @@ -325,8 +325,16 @@ message WatchEvent { } message SearchRequest { + // query string for chosen implementation (currently just bleve) string query = 1; - string tenant = 2; + // default to bleve + string queryType = 2; + string tenant = 3; + // resource kind (playlists, dashboards, etc) + string kind = 4; + // pagination support + int64 limit = 5; + int64 offset = 6; } message SearchResponse { diff --git a/pkg/storage/unified/resource/validation.go b/pkg/storage/unified/resource/validation.go index dce25a1b8fe..066ac42c2af 100644 --- a/pkg/storage/unified/resource/validation.go +++ b/pkg/storage/unified/resource/validation.go @@ -4,14 +4,14 @@ import ( "regexp" ) -var validNameCharPattern = `a-zA-Z0-9\-\_\.` +var validNameCharPattern = `a-zA-Z0-9:\-\_\.` var validNamePattern = regexp.MustCompile(`^[` + validNameCharPattern + `]*$`).MatchString func validateName(name string) *ErrorResult { if len(name) == 0 { return NewBadRequestError("name is too short") } - if len(name) > 64 { + if len(name) > 253 { return NewBadRequestError("name is too long") } if !validNamePattern(name) { diff --git a/pkg/storage/unified/resource/validation_test.go b/pkg/storage/unified/resource/validation_test.go index b9f9a51d7b5..bc0e4bd8bda 100644 --- a/pkg/storage/unified/resource/validation_test.go +++ b/pkg/storage/unified/resource/validation_test.go @@ -1,22 +1,22 @@ package resource import ( + "strings" "testing" "github.com/stretchr/testify/require" ) func TestNameValidation(t *testing.T) { - require.NotNil(t, validateName("")) // too short - require.NotNil(t, validateName( // too long (max 64) - "0123456789012345678901234567890123456789012345678901234567890123456789", - )) + require.NotNil(t, validateName("")) // too short + require.NotNil(t, validateName(strings.Repeat("0", 254))) // too long (max 253) // OK require.Nil(t, validateName("a")) require.Nil(t, validateName("hello-world")) require.Nil(t, validateName("hello.world")) require.Nil(t, validateName("hello_world")) + require.Nil(t, validateName("hello:world")) // Bad characters require.NotNil(t, validateName("hello world")) diff --git a/pkg/storage/unified/sql/backend.go b/pkg/storage/unified/sql/backend.go index 1a6e79e3257..556c860bbb7 100644 --- a/pkg/storage/unified/sql/backend.go +++ b/pkg/storage/unified/sql/backend.go @@ -10,18 +10,19 @@ import ( "time" "github.com/google/uuid" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" + "google.golang.org/protobuf/proto" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/sql/db" "github.com/grafana/grafana/pkg/storage/unified/sql/dbutil" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" - "go.opentelemetry.io/otel/trace" - "go.opentelemetry.io/otel/trace/noop" - "google.golang.org/protobuf/proto" - apierrors "k8s.io/apimachinery/pkg/api/errors" ) -const trace_prefix = "sql.resource." +const tracePrefix = "sql.resource." const defaultPollingInterval = 100 * time.Millisecond type Backend interface { @@ -119,7 +120,7 @@ func (b *backend) Stop(_ context.Context) error { } func (b *backend) WriteEvent(ctx context.Context, event resource.WriteEvent) (int64, error) { - _, span := b.tracer.Start(ctx, trace_prefix+"WriteEvent") + _, span := b.tracer.Start(ctx, tracePrefix+"WriteEvent") defer span.End() // TODO: validate key ? switch event.Type { @@ -135,7 +136,7 @@ func (b *backend) WriteEvent(ctx context.Context, event resource.WriteEvent) (in } func (b *backend) create(ctx context.Context, event resource.WriteEvent) (int64, error) { - ctx, span := b.tracer.Start(ctx, trace_prefix+"Create") + ctx, span := b.tracer.Start(ctx, tracePrefix+"Create") defer span.End() var newVersion int64 guid := uuid.New().String() @@ -192,7 +193,7 @@ func (b *backend) create(ctx context.Context, event resource.WriteEvent) (int64, } func (b *backend) update(ctx context.Context, event resource.WriteEvent) (int64, error) { - ctx, span := b.tracer.Start(ctx, trace_prefix+"Update") + ctx, span := b.tracer.Start(ctx, tracePrefix+"Update") defer span.End() var newVersion int64 guid := uuid.New().String() @@ -251,7 +252,7 @@ func (b *backend) update(ctx context.Context, event resource.WriteEvent) (int64, } func (b *backend) delete(ctx context.Context, event resource.WriteEvent) (int64, error) { - ctx, span := b.tracer.Start(ctx, trace_prefix+"Delete") + ctx, span := b.tracer.Start(ctx, tracePrefix+"Delete") defer span.End() var newVersion int64 guid := uuid.New().String() @@ -303,7 +304,7 @@ func (b *backend) delete(ctx context.Context, event resource.WriteEvent) (int64, } func (b *backend) ReadResource(ctx context.Context, req *resource.ReadRequest) *resource.ReadResponse { - _, span := b.tracer.Start(ctx, trace_prefix+".Read") + _, span := b.tracer.Start(ctx, tracePrefix+".Read") defer span.End() // TODO: validate key ? @@ -338,7 +339,7 @@ func (b *backend) ReadResource(ctx context.Context, req *resource.ReadRequest) * } func (b *backend) ListIterator(ctx context.Context, req *resource.ListRequest, cb func(resource.ListIterator) error) (int64, error) { - _, span := b.tracer.Start(ctx, trace_prefix+"List") + _, span := b.tracer.Start(ctx, tracePrefix+"List") defer span.End() if req.Options == nil || req.Options.Key.Group == "" || req.Options.Key.Resource == "" { @@ -608,7 +609,7 @@ func fetchLatestRV(ctx context.Context, x db.ContextExecer, d sqltemplate.Dialec } func (b *backend) poll(ctx context.Context, grp string, res string, since int64, stream chan<- *resource.WrittenEvent) (int64, error) { - ctx, span := b.tracer.Start(ctx, trace_prefix+"poll") + ctx, span := b.tracer.Start(ctx, tracePrefix+"poll") defer span.End() var records []*historyPollResponse diff --git a/pkg/storage/unified/sql/db/dbimpl/dbEngine.go b/pkg/storage/unified/sql/db/dbimpl/dbEngine.go index 3968cfc7839..46392d04cb6 100644 --- a/pkg/storage/unified/sql/db/dbimpl/dbEngine.go +++ b/pkg/storage/unified/sql/db/dbimpl/dbEngine.go @@ -9,11 +9,10 @@ import ( "github.com/go-sql-driver/mysql" "xorm.io/xorm" - "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/storage/unified/sql/db" ) -func getEngineMySQL(getter confGetter, tracer tracing.Tracer) (*xorm.Engine, error) { +func getEngineMySQL(getter confGetter) (*xorm.Engine, error) { config := mysql.NewConfig() config.User = getter.String("user") // accept the core Grafana jargon of `password` as well, originally Unified @@ -54,8 +53,6 @@ func getEngineMySQL(getter confGetter, tracer tracing.Tracer) (*xorm.Engine, err } // FIXME: get rid of xorm - // TODO figure out why wrapping the db driver with hooks causes mysql errors when writing - //driverName := sqlstore.WrapDatabaseDriverWithHooks(db.DriverMySQL, tracer) engine, err := xorm.NewEngine(db.DriverMySQL, config.FormatDSN()) if err != nil { return nil, fmt.Errorf("open database: %w", err) @@ -68,7 +65,7 @@ func getEngineMySQL(getter confGetter, tracer tracing.Tracer) (*xorm.Engine, err return engine, nil } -func getEnginePostgres(getter confGetter, tracer tracing.Tracer) (*xorm.Engine, error) { +func getEnginePostgres(getter confGetter) (*xorm.Engine, error) { dsnKV := map[string]string{ "user": getter.String("user"), // accept the core Grafana jargon of `password` as well, originally diff --git a/pkg/storage/unified/sql/db/dbimpl/dbEngine_test.go b/pkg/storage/unified/sql/db/dbimpl/dbEngine_test.go index 659d3bd1bb6..583e2bcdc99 100644 --- a/pkg/storage/unified/sql/db/dbimpl/dbEngine_test.go +++ b/pkg/storage/unified/sql/db/dbimpl/dbEngine_test.go @@ -25,14 +25,14 @@ func TestGetEngineMySQLFromConfig(t *testing.T) { t.Run("happy path - with key prefix", func(t *testing.T) { t.Parallel() - engine, err := getEngineMySQL(newValidMySQLGetter(true), nil) + engine, err := getEngineMySQL(newValidMySQLGetter(true)) assert.NotNil(t, engine) assert.NoError(t, err) }) t.Run("happy path - without key prefix", func(t *testing.T) { t.Parallel() - engine, err := getEngineMySQL(newValidMySQLGetter(false), nil) + engine, err := getEngineMySQL(newValidMySQLGetter(false)) assert.NotNil(t, engine) assert.NoError(t, err) }) @@ -47,7 +47,7 @@ func TestGetEngineMySQLFromConfig(t *testing.T) { "db_user": "user", "db_password": "password", }, "db_") - engine, err := getEngineMySQL(getter, nil) + engine, err := getEngineMySQL(getter) assert.Nil(t, engine) assert.Error(t, err) assert.ErrorIs(t, err, errInvalidUTF8Sequence) @@ -73,14 +73,14 @@ func TestGetEnginePostgresFromConfig(t *testing.T) { t.Run("happy path - with key prefix", func(t *testing.T) { t.Parallel() - engine, err := getEnginePostgres(newValidPostgresGetter(true), nil) + engine, err := getEnginePostgres(newValidPostgresGetter(true)) assert.NotNil(t, engine) assert.NoError(t, err) }) t.Run("happy path - without key prefix", func(t *testing.T) { t.Parallel() - engine, err := getEnginePostgres(newValidPostgresGetter(false), nil) + engine, err := getEnginePostgres(newValidPostgresGetter(false)) assert.NotNil(t, engine) assert.NoError(t, err) }) @@ -94,7 +94,7 @@ func TestGetEnginePostgresFromConfig(t *testing.T) { "db_user": "user", "db_password": "password", }, "db_") - engine, err := getEnginePostgres(getter, nil) + engine, err := getEnginePostgres(getter) assert.Nil(t, engine) assert.Error(t, err) @@ -110,7 +110,7 @@ func TestGetEnginePostgresFromConfig(t *testing.T) { "db_user": "user", "db_password": "password", }, "db_") - engine, err := getEnginePostgres(getter, nil) + engine, err := getEnginePostgres(getter) assert.Nil(t, engine) assert.Error(t, err) diff --git a/pkg/storage/unified/sql/db/dbimpl/dbimpl.go b/pkg/storage/unified/sql/db/dbimpl/dbimpl.go index 4756a1f4d56..2264ad3c8a3 100644 --- a/pkg/storage/unified/sql/db/dbimpl/dbimpl.go +++ b/pkg/storage/unified/sql/db/dbimpl/dbimpl.go @@ -9,6 +9,7 @@ import ( "github.com/dlmiddlecote/sqlstats" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/prometheus/client_golang/prometheus" + "go.opentelemetry.io/otel/trace" "xorm.io/xorm" infraDB "github.com/grafana/grafana/pkg/infra/db" @@ -63,7 +64,7 @@ type resourceDBProvider struct { logQueries bool } -func newResourceDBProvider(grafanaDB infraDB.DB, cfg *setting.Cfg, tracer tracing.Tracer) (p *resourceDBProvider, err error) { +func newResourceDBProvider(grafanaDB infraDB.DB, cfg *setting.Cfg, tracer trace.Tracer) (p *resourceDBProvider, err error) { // Resource API has other configs in its section besides database ones, so // we prefix them with "db_". We use the database config from core Grafana // as fallback, and as it uses a dedicated INI section, then keys are not @@ -85,12 +86,12 @@ func newResourceDBProvider(grafanaDB infraDB.DB, cfg *setting.Cfg, tracer tracin // specific to Unified Storage case dbType == dbTypePostgres: p.registerMetrics = true - p.engine, err = getEnginePostgres(getter, tracer) + p.engine, err = getEnginePostgres(getter) return p, err case dbType == dbTypeMySQL: p.registerMetrics = true - p.engine, err = getEngineMySQL(getter, tracer) + p.engine, err = getEngineMySQL(getter) return p, err // TODO: add support for SQLite @@ -103,12 +104,12 @@ func newResourceDBProvider(grafanaDB infraDB.DB, cfg *setting.Cfg, tracer tracin case grafanaDBType == dbTypePostgres: p.registerMetrics = true - p.engine, err = getEnginePostgres(fallbackGetter, tracer) + p.engine, err = getEnginePostgres(fallbackGetter) return p, err case grafanaDBType == dbTypeMySQL: p.registerMetrics = true - p.engine, err = getEngineMySQL(fallbackGetter, tracer) + p.engine, err = getEngineMySQL(fallbackGetter) return p, err // TODO: add support for SQLite diff --git a/pkg/storage/unified/sql/db/dbimpl/driver_test.go b/pkg/storage/unified/sql/db/dbimpl/driver_test.go new file mode 100644 index 00000000000..b52f70e1197 --- /dev/null +++ b/pkg/storage/unified/sql/db/dbimpl/driver_test.go @@ -0,0 +1,95 @@ +package dbimpl + +import ( + "context" + "database/sql" + "database/sql/driver" + "sync" +) + +var _ driver.Driver = driverWithoutIsolationLevel{} +var _ driver.Driver = driverWithIsolationLevel{} + +const ( + driverWithoutIsolationLevelName = "test driver without isolation levels" + driverWithIsolationLevelName = "test driver with isolation levels" +) + +var registerTestDriversOnce sync.Once + +func registerTestSQLDrivers() { + registerTestDriversOnce.Do(func() { + sql.Register(driverWithoutIsolationLevelName, driverWithoutIsolationLevel{}) + sql.Register(driverWithIsolationLevelName, driverWithIsolationLevel{}) + }) +} + +type ( + // without isolation level + + driverWithoutIsolationLevel struct{} + connWithoutIsolationLevel struct{} + + // with isolation level + + driverWithIsolationLevel struct{} + connWithIsolationLevel struct { + connWithoutIsolationLevel + } + + // common + + testStmt struct{} + testTx struct{} + testResults struct{} + testRows struct{} +) + +// driver.Driver + +func (driverWithoutIsolationLevel) Open(name string) (driver.Conn, error) { + return connWithoutIsolationLevel{}, nil +} + +func (driverWithIsolationLevel) Open(name string) (driver.Conn, error) { + return connWithIsolationLevel{}, nil +} + +// driver.Conn + +func (connWithoutIsolationLevel) Prepare(query string) (driver.Stmt, error) { + return testStmt{}, nil +} +func (connWithoutIsolationLevel) Close() error { + return nil +} +func (connWithoutIsolationLevel) Begin() (driver.Tx, error) { + return testTx{}, nil +} + +func (connWithIsolationLevel) BeginTx(context.Context, driver.TxOptions) (driver.Tx, error) { + return testTx{}, nil +} + +// driver.Stmt + +func (testStmt) Close() error { return nil } +func (testStmt) NumInput() int { return 0 } +func (testStmt) Exec(args []driver.Value) (driver.Result, error) { return testResults{}, nil } +func (testStmt) Query(args []driver.Value) (driver.Rows, error) { return testRows{}, nil } + +// driver.Tx + +func (testTx) Commit() error { return nil } +func (testTx) Rollback() error { return nil } + +// driver.Results + +func (testResults) LastInsertId() (int64, error) { return 1, nil } +func (testResults) RowsAffected() (int64, error) { return 1, nil } + +// driver.Rows + +func (testRows) Columns() []string { return nil } +func (testRows) Close() error { return nil } +func (testRows) Next(dest []driver.Value) error { return nil } diff --git a/pkg/storage/unified/sql/db/dbimpl/regression_incident_2144_test.go b/pkg/storage/unified/sql/db/dbimpl/regression_incident_2144_test.go index 1d0b6ba9b02..ad29caf8cd2 100644 --- a/pkg/storage/unified/sql/db/dbimpl/regression_incident_2144_test.go +++ b/pkg/storage/unified/sql/db/dbimpl/regression_incident_2144_test.go @@ -1,10 +1,7 @@ package dbimpl import ( - "context" "database/sql" - "database/sql/driver" - "sync" "testing" "github.com/stretchr/testify/require" @@ -16,93 +13,6 @@ import ( const noIsolationLevelSupportErrStr = "sql: driver does not support non-" + "default isolation level" -var _ driver.Driver = driverWithoutIsolationLevel{} -var _ driver.Driver = driverWithIsolationLevel{} - -const ( - driverWithoutIsolationLevelName = "test driver without isolation levels" - driverWithIsolationLevelName = "test driver with isolation levels" -) - -var registerTestDriversOnce sync.Once - -func registerTestSQLDrivers() { - registerTestDriversOnce.Do(func() { - sql.Register(driverWithoutIsolationLevelName, driverWithoutIsolationLevel{}) - sql.Register(driverWithIsolationLevelName, driverWithIsolationLevel{}) - }) -} - -type ( - // without isolation level - - driverWithoutIsolationLevel struct{} - connWithoutIsolationLevel struct{} - - // with isolation level - - driverWithIsolationLevel struct{} - connWithIsolationLevel struct { - connWithoutIsolationLevel - } - - // common - - testStmt struct{} - testTx struct{} - testResults struct{} - testRows struct{} -) - -// driver.Driver - -func (driverWithoutIsolationLevel) Open(name string) (driver.Conn, error) { - return connWithoutIsolationLevel{}, nil -} - -func (driverWithIsolationLevel) Open(name string) (driver.Conn, error) { - return connWithIsolationLevel{}, nil -} - -// driver.Conn - -func (connWithoutIsolationLevel) Prepare(query string) (driver.Stmt, error) { - return testStmt{}, nil -} -func (connWithoutIsolationLevel) Close() error { - return nil -} -func (connWithoutIsolationLevel) Begin() (driver.Tx, error) { - return testTx{}, nil -} - -func (connWithIsolationLevel) BeginTx(context.Context, driver.TxOptions) (driver.Tx, error) { - return testTx{}, nil -} - -// driver.Stmt - -func (testStmt) Close() error { return nil } -func (testStmt) NumInput() int { return 0 } -func (testStmt) Exec(args []driver.Value) (driver.Result, error) { return testResults{}, nil } -func (testStmt) Query(args []driver.Value) (driver.Rows, error) { return testRows{}, nil } - -// driver.Tx - -func (testTx) Commit() error { return nil } -func (testTx) Rollback() error { return nil } - -// driver.Results - -func (testResults) LastInsertId() (int64, error) { return 1, nil } -func (testResults) RowsAffected() (int64, error) { return 1, nil } - -// driver.Rows - -func (testRows) Columns() []string { return nil } -func (testRows) Close() error { return nil } -func (testRows) Next(dest []driver.Value) error { return nil } - func TestReproIncident2144IndependentOfGrafanaDB(t *testing.T) { t.Parallel() registerTestSQLDrivers() diff --git a/pkg/storage/unified/sql/db/migrations/resource_mig.go b/pkg/storage/unified/sql/db/migrations/resource_mig.go index 15e13c08a38..fcaddaacc80 100644 --- a/pkg/storage/unified/sql/db/migrations/resource_mig.go +++ b/pkg/storage/unified/sql/db/migrations/resource_mig.go @@ -22,7 +22,7 @@ func initResourceTables(mg *migrator.Migrator) string { {Name: "group", Type: migrator.DB_NVarchar, Length: 190, Nullable: false}, {Name: "resource", Type: migrator.DB_NVarchar, Length: 190, Nullable: false}, {Name: "namespace", Type: migrator.DB_NVarchar, Length: 63, Nullable: false}, - {Name: "name", Type: migrator.DB_NVarchar, Length: 190, Nullable: false}, + {Name: "name", Type: migrator.DB_NVarchar, Length: 253, Nullable: false}, {Name: "value", Type: migrator.DB_LongText, Nullable: true}, {Name: "action", Type: migrator.DB_Int, Nullable: false}, // 1: create, 2: update, 3: delete @@ -44,7 +44,7 @@ func initResourceTables(mg *migrator.Migrator) string { {Name: "group", Type: migrator.DB_NVarchar, Length: 190, Nullable: false}, {Name: "resource", Type: migrator.DB_NVarchar, Length: 190, Nullable: false}, {Name: "namespace", Type: migrator.DB_NVarchar, Length: 63, Nullable: false}, - {Name: "name", Type: migrator.DB_NVarchar, Length: 190, Nullable: false}, + {Name: "name", Type: migrator.DB_NVarchar, Length: 253, Nullable: false}, {Name: "value", Type: migrator.DB_LongText, Nullable: true}, {Name: "action", Type: migrator.DB_Int, Nullable: false}, // 1: create, 2: update, 3: delete diff --git a/pkg/storage/unified/sql/db/mocks/DB.go b/pkg/storage/unified/sql/db/mocks/DB.go new file mode 100644 index 00000000000..fa86325a6aa --- /dev/null +++ b/pkg/storage/unified/sql/db/mocks/DB.go @@ -0,0 +1,482 @@ +// Code generated by mockery v2.43.1. DO NOT EDIT. + +package mocks + +import ( + context "context" + + db "github.com/grafana/grafana/pkg/storage/unified/sql/db" + mock "github.com/stretchr/testify/mock" + + sql "database/sql" +) + +// DB is an autogenerated mock type for the DB type +type DB struct { + mock.Mock +} + +type DB_Expecter struct { + mock *mock.Mock +} + +func (_m *DB) EXPECT() *DB_Expecter { + return &DB_Expecter{mock: &_m.Mock} +} + +// BeginTx provides a mock function with given fields: _a0, _a1 +func (_m *DB) BeginTx(_a0 context.Context, _a1 *sql.TxOptions) (db.Tx, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for BeginTx") + } + + var r0 db.Tx + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *sql.TxOptions) (db.Tx, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *sql.TxOptions) db.Tx); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(db.Tx) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *sql.TxOptions) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DB_BeginTx_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BeginTx' +type DB_BeginTx_Call struct { + *mock.Call +} + +// BeginTx is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *sql.TxOptions +func (_e *DB_Expecter) BeginTx(_a0 interface{}, _a1 interface{}) *DB_BeginTx_Call { + return &DB_BeginTx_Call{Call: _e.mock.On("BeginTx", _a0, _a1)} +} + +func (_c *DB_BeginTx_Call) Run(run func(_a0 context.Context, _a1 *sql.TxOptions)) *DB_BeginTx_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*sql.TxOptions)) + }) + return _c +} + +func (_c *DB_BeginTx_Call) Return(_a0 db.Tx, _a1 error) *DB_BeginTx_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DB_BeginTx_Call) RunAndReturn(run func(context.Context, *sql.TxOptions) (db.Tx, error)) *DB_BeginTx_Call { + _c.Call.Return(run) + return _c +} + +// DriverName provides a mock function with given fields: +func (_m *DB) DriverName() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for DriverName") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// DB_DriverName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DriverName' +type DB_DriverName_Call struct { + *mock.Call +} + +// DriverName is a helper method to define mock.On call +func (_e *DB_Expecter) DriverName() *DB_DriverName_Call { + return &DB_DriverName_Call{Call: _e.mock.On("DriverName")} +} + +func (_c *DB_DriverName_Call) Run(run func()) *DB_DriverName_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *DB_DriverName_Call) Return(_a0 string) *DB_DriverName_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *DB_DriverName_Call) RunAndReturn(run func() string) *DB_DriverName_Call { + _c.Call.Return(run) + return _c +} + +// ExecContext provides a mock function with given fields: ctx, query, args +func (_m *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { + var _ca []interface{} + _ca = append(_ca, ctx, query) + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for ExecContext") + } + + var r0 sql.Result + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) (sql.Result, error)); ok { + return rf(ctx, query, args...) + } + if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) sql.Result); ok { + r0 = rf(ctx, query, args...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(sql.Result) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, ...interface{}) error); ok { + r1 = rf(ctx, query, args...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DB_ExecContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ExecContext' +type DB_ExecContext_Call struct { + *mock.Call +} + +// ExecContext is a helper method to define mock.On call +// - ctx context.Context +// - query string +// - args ...interface{} +func (_e *DB_Expecter) ExecContext(ctx interface{}, query interface{}, args ...interface{}) *DB_ExecContext_Call { + return &DB_ExecContext_Call{Call: _e.mock.On("ExecContext", + append([]interface{}{ctx, query}, args...)...)} +} + +func (_c *DB_ExecContext_Call) Run(run func(ctx context.Context, query string, args ...interface{})) *DB_ExecContext_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(context.Context), args[1].(string), variadicArgs...) + }) + return _c +} + +func (_c *DB_ExecContext_Call) Return(_a0 sql.Result, _a1 error) *DB_ExecContext_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DB_ExecContext_Call) RunAndReturn(run func(context.Context, string, ...interface{}) (sql.Result, error)) *DB_ExecContext_Call { + _c.Call.Return(run) + return _c +} + +// PingContext provides a mock function with given fields: _a0 +func (_m *DB) PingContext(_a0 context.Context) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for PingContext") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DB_PingContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PingContext' +type DB_PingContext_Call struct { + *mock.Call +} + +// PingContext is a helper method to define mock.On call +// - _a0 context.Context +func (_e *DB_Expecter) PingContext(_a0 interface{}) *DB_PingContext_Call { + return &DB_PingContext_Call{Call: _e.mock.On("PingContext", _a0)} +} + +func (_c *DB_PingContext_Call) Run(run func(_a0 context.Context)) *DB_PingContext_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *DB_PingContext_Call) Return(_a0 error) *DB_PingContext_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *DB_PingContext_Call) RunAndReturn(run func(context.Context) error) *DB_PingContext_Call { + _c.Call.Return(run) + return _c +} + +// QueryContext provides a mock function with given fields: ctx, query, args +func (_m *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { + var _ca []interface{} + _ca = append(_ca, ctx, query) + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for QueryContext") + } + + var r0 *sql.Rows + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) (*sql.Rows, error)); ok { + return rf(ctx, query, args...) + } + if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) *sql.Rows); ok { + r0 = rf(ctx, query, args...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*sql.Rows) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, ...interface{}) error); ok { + r1 = rf(ctx, query, args...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DB_QueryContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QueryContext' +type DB_QueryContext_Call struct { + *mock.Call +} + +// QueryContext is a helper method to define mock.On call +// - ctx context.Context +// - query string +// - args ...interface{} +func (_e *DB_Expecter) QueryContext(ctx interface{}, query interface{}, args ...interface{}) *DB_QueryContext_Call { + return &DB_QueryContext_Call{Call: _e.mock.On("QueryContext", + append([]interface{}{ctx, query}, args...)...)} +} + +func (_c *DB_QueryContext_Call) Run(run func(ctx context.Context, query string, args ...interface{})) *DB_QueryContext_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(context.Context), args[1].(string), variadicArgs...) + }) + return _c +} + +func (_c *DB_QueryContext_Call) Return(_a0 *sql.Rows, _a1 error) *DB_QueryContext_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DB_QueryContext_Call) RunAndReturn(run func(context.Context, string, ...interface{}) (*sql.Rows, error)) *DB_QueryContext_Call { + _c.Call.Return(run) + return _c +} + +// QueryRowContext provides a mock function with given fields: ctx, query, args +func (_m *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row { + var _ca []interface{} + _ca = append(_ca, ctx, query) + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for QueryRowContext") + } + + var r0 *sql.Row + if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) *sql.Row); ok { + r0 = rf(ctx, query, args...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*sql.Row) + } + } + + return r0 +} + +// DB_QueryRowContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QueryRowContext' +type DB_QueryRowContext_Call struct { + *mock.Call +} + +// QueryRowContext is a helper method to define mock.On call +// - ctx context.Context +// - query string +// - args ...interface{} +func (_e *DB_Expecter) QueryRowContext(ctx interface{}, query interface{}, args ...interface{}) *DB_QueryRowContext_Call { + return &DB_QueryRowContext_Call{Call: _e.mock.On("QueryRowContext", + append([]interface{}{ctx, query}, args...)...)} +} + +func (_c *DB_QueryRowContext_Call) Run(run func(ctx context.Context, query string, args ...interface{})) *DB_QueryRowContext_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(context.Context), args[1].(string), variadicArgs...) + }) + return _c +} + +func (_c *DB_QueryRowContext_Call) Return(_a0 *sql.Row) *DB_QueryRowContext_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *DB_QueryRowContext_Call) RunAndReturn(run func(context.Context, string, ...interface{}) *sql.Row) *DB_QueryRowContext_Call { + _c.Call.Return(run) + return _c +} + +// Stats provides a mock function with given fields: +func (_m *DB) Stats() sql.DBStats { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Stats") + } + + var r0 sql.DBStats + if rf, ok := ret.Get(0).(func() sql.DBStats); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(sql.DBStats) + } + + return r0 +} + +// DB_Stats_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stats' +type DB_Stats_Call struct { + *mock.Call +} + +// Stats is a helper method to define mock.On call +func (_e *DB_Expecter) Stats() *DB_Stats_Call { + return &DB_Stats_Call{Call: _e.mock.On("Stats")} +} + +func (_c *DB_Stats_Call) Run(run func()) *DB_Stats_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *DB_Stats_Call) Return(_a0 sql.DBStats) *DB_Stats_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *DB_Stats_Call) RunAndReturn(run func() sql.DBStats) *DB_Stats_Call { + _c.Call.Return(run) + return _c +} + +// WithTx provides a mock function with given fields: _a0, _a1, _a2 +func (_m *DB) WithTx(_a0 context.Context, _a1 *sql.TxOptions, _a2 func(context.Context, db.Tx) error) error { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for WithTx") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *sql.TxOptions, func(context.Context, db.Tx) error) error); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DB_WithTx_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WithTx' +type DB_WithTx_Call struct { + *mock.Call +} + +// WithTx is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *sql.TxOptions +// - _a2 func(context.Context , db.Tx) error +func (_e *DB_Expecter) WithTx(_a0 interface{}, _a1 interface{}, _a2 interface{}) *DB_WithTx_Call { + return &DB_WithTx_Call{Call: _e.mock.On("WithTx", _a0, _a1, _a2)} +} + +func (_c *DB_WithTx_Call) Run(run func(_a0 context.Context, _a1 *sql.TxOptions, _a2 func(context.Context, db.Tx) error)) *DB_WithTx_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*sql.TxOptions), args[2].(func(context.Context, db.Tx) error)) + }) + return _c +} + +func (_c *DB_WithTx_Call) Return(_a0 error) *DB_WithTx_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *DB_WithTx_Call) RunAndReturn(run func(context.Context, *sql.TxOptions, func(context.Context, db.Tx) error) error) *DB_WithTx_Call { + _c.Call.Return(run) + return _c +} + +// NewDB creates a new instance of DB. 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 NewDB(t interface { + mock.TestingT + Cleanup(func()) +}) *DB { + mock := &DB{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/storage/unified/sql/db/mocks/Tx.go b/pkg/storage/unified/sql/db/mocks/Tx.go new file mode 100644 index 00000000000..2927a125fae --- /dev/null +++ b/pkg/storage/unified/sql/db/mocks/Tx.go @@ -0,0 +1,328 @@ +// Code generated by mockery v2.43.1. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + sql "database/sql" +) + +// Tx is an autogenerated mock type for the Tx type +type Tx struct { + mock.Mock +} + +type Tx_Expecter struct { + mock *mock.Mock +} + +func (_m *Tx) EXPECT() *Tx_Expecter { + return &Tx_Expecter{mock: &_m.Mock} +} + +// Commit provides a mock function with given fields: +func (_m *Tx) Commit() error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Commit") + } + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Tx_Commit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Commit' +type Tx_Commit_Call struct { + *mock.Call +} + +// Commit is a helper method to define mock.On call +func (_e *Tx_Expecter) Commit() *Tx_Commit_Call { + return &Tx_Commit_Call{Call: _e.mock.On("Commit")} +} + +func (_c *Tx_Commit_Call) Run(run func()) *Tx_Commit_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Tx_Commit_Call) Return(_a0 error) *Tx_Commit_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Tx_Commit_Call) RunAndReturn(run func() error) *Tx_Commit_Call { + _c.Call.Return(run) + return _c +} + +// ExecContext provides a mock function with given fields: ctx, query, args +func (_m *Tx) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { + var _ca []interface{} + _ca = append(_ca, ctx, query) + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for ExecContext") + } + + var r0 sql.Result + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) (sql.Result, error)); ok { + return rf(ctx, query, args...) + } + if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) sql.Result); ok { + r0 = rf(ctx, query, args...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(sql.Result) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, ...interface{}) error); ok { + r1 = rf(ctx, query, args...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Tx_ExecContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ExecContext' +type Tx_ExecContext_Call struct { + *mock.Call +} + +// ExecContext is a helper method to define mock.On call +// - ctx context.Context +// - query string +// - args ...interface{} +func (_e *Tx_Expecter) ExecContext(ctx interface{}, query interface{}, args ...interface{}) *Tx_ExecContext_Call { + return &Tx_ExecContext_Call{Call: _e.mock.On("ExecContext", + append([]interface{}{ctx, query}, args...)...)} +} + +func (_c *Tx_ExecContext_Call) Run(run func(ctx context.Context, query string, args ...interface{})) *Tx_ExecContext_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(context.Context), args[1].(string), variadicArgs...) + }) + return _c +} + +func (_c *Tx_ExecContext_Call) Return(_a0 sql.Result, _a1 error) *Tx_ExecContext_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Tx_ExecContext_Call) RunAndReturn(run func(context.Context, string, ...interface{}) (sql.Result, error)) *Tx_ExecContext_Call { + _c.Call.Return(run) + return _c +} + +// QueryContext provides a mock function with given fields: ctx, query, args +func (_m *Tx) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { + var _ca []interface{} + _ca = append(_ca, ctx, query) + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for QueryContext") + } + + var r0 *sql.Rows + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) (*sql.Rows, error)); ok { + return rf(ctx, query, args...) + } + if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) *sql.Rows); ok { + r0 = rf(ctx, query, args...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*sql.Rows) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, ...interface{}) error); ok { + r1 = rf(ctx, query, args...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Tx_QueryContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QueryContext' +type Tx_QueryContext_Call struct { + *mock.Call +} + +// QueryContext is a helper method to define mock.On call +// - ctx context.Context +// - query string +// - args ...interface{} +func (_e *Tx_Expecter) QueryContext(ctx interface{}, query interface{}, args ...interface{}) *Tx_QueryContext_Call { + return &Tx_QueryContext_Call{Call: _e.mock.On("QueryContext", + append([]interface{}{ctx, query}, args...)...)} +} + +func (_c *Tx_QueryContext_Call) Run(run func(ctx context.Context, query string, args ...interface{})) *Tx_QueryContext_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(context.Context), args[1].(string), variadicArgs...) + }) + return _c +} + +func (_c *Tx_QueryContext_Call) Return(_a0 *sql.Rows, _a1 error) *Tx_QueryContext_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Tx_QueryContext_Call) RunAndReturn(run func(context.Context, string, ...interface{}) (*sql.Rows, error)) *Tx_QueryContext_Call { + _c.Call.Return(run) + return _c +} + +// QueryRowContext provides a mock function with given fields: ctx, query, args +func (_m *Tx) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row { + var _ca []interface{} + _ca = append(_ca, ctx, query) + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for QueryRowContext") + } + + var r0 *sql.Row + if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) *sql.Row); ok { + r0 = rf(ctx, query, args...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*sql.Row) + } + } + + return r0 +} + +// Tx_QueryRowContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QueryRowContext' +type Tx_QueryRowContext_Call struct { + *mock.Call +} + +// QueryRowContext is a helper method to define mock.On call +// - ctx context.Context +// - query string +// - args ...interface{} +func (_e *Tx_Expecter) QueryRowContext(ctx interface{}, query interface{}, args ...interface{}) *Tx_QueryRowContext_Call { + return &Tx_QueryRowContext_Call{Call: _e.mock.On("QueryRowContext", + append([]interface{}{ctx, query}, args...)...)} +} + +func (_c *Tx_QueryRowContext_Call) Run(run func(ctx context.Context, query string, args ...interface{})) *Tx_QueryRowContext_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(context.Context), args[1].(string), variadicArgs...) + }) + return _c +} + +func (_c *Tx_QueryRowContext_Call) Return(_a0 *sql.Row) *Tx_QueryRowContext_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Tx_QueryRowContext_Call) RunAndReturn(run func(context.Context, string, ...interface{}) *sql.Row) *Tx_QueryRowContext_Call { + _c.Call.Return(run) + return _c +} + +// Rollback provides a mock function with given fields: +func (_m *Tx) Rollback() error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Rollback") + } + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Tx_Rollback_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Rollback' +type Tx_Rollback_Call struct { + *mock.Call +} + +// Rollback is a helper method to define mock.On call +func (_e *Tx_Expecter) Rollback() *Tx_Rollback_Call { + return &Tx_Rollback_Call{Call: _e.mock.On("Rollback")} +} + +func (_c *Tx_Rollback_Call) Run(run func()) *Tx_Rollback_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Tx_Rollback_Call) Return(_a0 error) *Tx_Rollback_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Tx_Rollback_Call) RunAndReturn(run func() error) *Tx_Rollback_Call { + _c.Call.Return(run) + return _c +} + +// NewTx creates a new instance of Tx. 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 NewTx(t interface { + mock.TestingT + Cleanup(func()) +}) *Tx { + mock := &Tx{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/storage/unified/sql/db/service.go b/pkg/storage/unified/sql/db/service.go index dfd9019789d..09f4c664b67 100755 --- a/pkg/storage/unified/sql/db/service.go +++ b/pkg/storage/unified/sql/db/service.go @@ -5,6 +5,9 @@ import ( "database/sql" ) +//go:generate mockery --with-expecter --name DB +//go:generate mockery --with-expecter --name Tx + const ( DriverPostgres = "postgres" DriverMySQL = "mysql" diff --git a/pkg/tests/apis/folder/folders_test.go b/pkg/tests/apis/folder/folders_test.go index aea2069d750..9683eb03cbb 100644 --- a/pkg/tests/apis/folder/folders_test.go +++ b/pkg/tests/apis/folder/folders_test.go @@ -3,6 +3,7 @@ package playlist import ( "context" "encoding/json" + "fmt" "net/http" "slices" @@ -13,6 +14,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" + "github.com/grafana/grafana/pkg/api/dtos" folderv0alpha1 "github.com/grafana/grafana/pkg/apis/folder/v0alpha1" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -286,6 +288,24 @@ func TestIntegrationFoldersApp(t *testing.T) { doFolderTests(t, helper) }) + + t.Run("with dual write (unified storage, mode 1, nested folders)", func(t *testing.T) { + checkNestedCreate(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "unified", + UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ + folderv0alpha1.RESOURCEGROUP: { + DualWriterMode: grafanarest.Mode1, + }, + }, + EnableFeatureToggles: []string{ + featuremgmt.FlagGrafanaAPIServerTestingWithExperimentalAPIs, + featuremgmt.FlagNestedFolders, + featuremgmt.FlagKubernetesFolders, + }, + })) + }) } func doFolderTests(t *testing.T, helper *apis.K8sTestHelper) *apis.K8sTestHelper { @@ -368,6 +388,49 @@ func doFolderTests(t *testing.T, helper *apis.K8sTestHelper) *apis.K8sTestHelper return helper } +func checkNestedCreate(t *testing.T, helper *apis.K8sTestHelper) { + client := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + GVR: gvr, + }) + + parentPayload := `{ + "title": "Test/parent", + "uid": "" + }` + parentCreate := apis.DoRequest(helper, apis.RequestParams{ + User: client.Args.User, + Method: http.MethodPost, + Path: "/api/folders", + Body: []byte(parentPayload), + }, &folder.Folder{}) + require.NotNil(t, parentCreate.Result) + parentUID := parentCreate.Result.UID + require.NotEmpty(t, parentUID) + + childPayload := fmt.Sprintf(`{ + "title": "Test/child", + "uid": "", + "parentUid": "%s" + }`, parentUID) + childCreate := apis.DoRequest(helper, apis.RequestParams{ + User: client.Args.User, + Method: http.MethodPost, + Path: "/api/folders", + Body: []byte(childPayload), + }, &dtos.Folder{}) + require.NotNil(t, childCreate.Result) + childUID := childCreate.Result.UID + require.NotEmpty(t, childUID) + require.Equal(t, "Test/child", childCreate.Result.Title) + require.Equal(t, 1, len(childCreate.Result.Parents)) + + parent := childCreate.Result.Parents[0] + require.Equal(t, parentUID, parent.UID) + require.Equal(t, "Test\\/parent", parent.Title) + require.Equal(t, parentCreate.Result.URL, parent.URL) +} + // This does a get with both k8s and legacy API, and verifies the results are the same func getFromBothAPIs(t *testing.T, helper *apis.K8sTestHelper, diff --git a/pkg/tests/apis/query/query_test.go b/pkg/tests/apis/query/query_test.go index 02e63b6ca02..515c2910f15 100644 --- a/pkg/tests/apis/query/query_test.go +++ b/pkg/tests/apis/query/query_test.go @@ -143,9 +143,8 @@ func TestIntegrationSimpleQuery(t *testing.T) { "apiVersion": "v1", "metadata": {}, "status": "Failure", - "message": "did not execute expression [Y] due to a failure to of the dependent expression or query [X]", - "reason": "BadRequest", - "details": { "uid": "sse.dependencyError" }, + "message": "[sse.dependencyError] did not execute expression [Y] due to a failure to of the dependent expression or query [X]", + "reason": "Invalid", "code": 400 }`, string(body)) // require.JSONEq(t, `{ diff --git a/pkg/tests/apis/scopes/scopes_test.go b/pkg/tests/apis/scopes/scopes_test.go index 87e8abb1318..eb380937dff 100644 --- a/pkg/tests/apis/scopes/scopes_test.go +++ b/pkg/tests/apis/scopes/scopes_test.go @@ -3,6 +3,7 @@ package scopes import ( "context" "encoding/json" + "strings" "testing" "github.com/stretchr/testify/require" @@ -159,6 +160,25 @@ func TestIntegrationScopes(t *testing.T) { ) require.NoError(t, err) + // Name length test + scope3 := helper.LoadYAMLOrJSONFile("testdata/example-scope3.yaml") + + // Name too long (>253) + scope3.SetName(strings.Repeat("0", 254)) + _, err = scopeClient.Resource.Create(ctx, + scope3, + createOptions, + ) + require.Error(t, err) + + // Maximum allowed length for name (253) + scope3.SetName(strings.Repeat("0", 253)) + _, err = scopeClient.Resource.Create(ctx, + scope3, + createOptions, + ) + require.NoError(t, err) + // Field Selector test found, err := scopeClient.Resource.List(ctx, metav1.ListOptions{ FieldSelector: "spec.title=foo-scope", diff --git a/pkg/tests/apis/scopes/testdata/example-scope3.yaml b/pkg/tests/apis/scopes/testdata/example-scope3.yaml new file mode 100644 index 00000000000..e1f36c39d87 --- /dev/null +++ b/pkg/tests/apis/scopes/testdata/example-scope3.yaml @@ -0,0 +1,14 @@ +apiVersion: scope.grafana.app/v0alpha1 +kind: Scope +metadata: + name: example-long +spec: + title: baz-scope + description: Longer description for a scope + filters: + - key: aaa + operator: equals + value: eee + - key: ccc + operator: not-equals + value: fff diff --git a/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go b/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go index f06ef71f098..6785a0addd8 100644 --- a/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go +++ b/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go @@ -393,7 +393,7 @@ func addDataLinksToFields(query *AzureLogAnalyticsQuery, azurePortalBaseUrl stri } func addTraceDataLinksToFields(query *AzureLogAnalyticsQuery, azurePortalBaseUrl string, frame *data.Frame, dsInfo types.DatasourceInfo) error { - tracesUrl, err := getTracesQueryUrl(query.Resources, azurePortalBaseUrl) + tracesUrl, err := getTracesQueryUrl(azurePortalBaseUrl) if err != nil { return err } @@ -552,20 +552,12 @@ func getQueryUrl(query string, resources []string, azurePortalUrl string, timeRa return portalUrl, nil } -func getTracesQueryUrl(resources []string, azurePortalUrl string) (string, error) { +func getTracesQueryUrl(azurePortalUrl string) (string, error) { portalUrl := azurePortalUrl portalUrl += "/#view/AppInsightsExtension/DetailsV2Blade/ComponentId~/" - resource := struct { - ResourceId string `json:"ResourceId"` - }{ - resources[0], - } - resourceMarshalled, err := json.Marshal(resource) - if err != nil { - return "", fmt.Errorf("failed to marshal application insights resource: %s", err) - } - portalUrl += url.PathEscape(string(resourceMarshalled)) + resource := "%7B%22ResourceId%22:%22${__data.fields.resource:percentencode}%22%7D" + portalUrl += resource portalUrl += "/DataModel~/" // We're making use of data link variables to select the necessary fields in the frontend diff --git a/pkg/tsdb/azuremonitor/loganalytics/traces.go b/pkg/tsdb/azuremonitor/loganalytics/traces.go index e5c2824ca07..e9b7ddf5078 100644 --- a/pkg/tsdb/azuremonitor/loganalytics/traces.go +++ b/pkg/tsdb/azuremonitor/loganalytics/traces.go @@ -114,8 +114,8 @@ func buildTracesQuery(operationId string, parentSpanID *string, traceTypes []str `| extend serviceName = cloud_RoleName` + `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` propertiesQuery := fmt.Sprintf(`| extend tags = %s`, propertiesFunc) - projectClause := `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + projectClause := `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc` return baseQuery + whereClause + parentWhereClause + propertiesStaticQuery + errorProperty + propertiesQuery + filtersClause + projectClause } diff --git a/pkg/tsdb/azuremonitor/loganalytics/traces_test.go b/pkg/tsdb/azuremonitor/loganalytics/traces_test.go index da069c41db4..3e365061c6d 100644 --- a/pkg/tsdb/azuremonitor/loganalytics/traces_test.go +++ b/pkg/tsdb/azuremonitor/loganalytics/traces_test.go @@ -135,8 +135,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1"}, TimeRange: timeRange, @@ -150,8 +150,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true trace` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + @@ -163,8 +163,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"test-op-id\"", @@ -210,8 +210,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1"}, TimeRange: timeRange, @@ -225,8 +225,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true trace` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + @@ -238,8 +238,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"test-op-id\"", @@ -282,8 +282,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1"}, TimeRange: timeRange, @@ -297,8 +297,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces` + `| where (operation_Id != '' and operation_Id == '${__data.fields.traceID}') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == '${__data.fields.traceID}')` + @@ -310,8 +310,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"${__data.fields.traceID}\"", @@ -357,8 +357,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1"}, TimeRange: timeRange, @@ -372,8 +372,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + @@ -385,8 +385,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"test-op-id\"", @@ -435,8 +435,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| where appId in ("test-app-id")` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1"}, TimeRange: timeRange, @@ -451,8 +451,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| where appId in ("test-app-id")` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + @@ -465,8 +465,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| where appId in ("test-app-id")` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"test-op-id\"", @@ -515,8 +515,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| where appId !in ("test-app-id")` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1"}, TimeRange: timeRange, @@ -531,8 +531,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| where appId !in ("test-app-id")` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + @@ -545,8 +545,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| where appId !in ("test-app-id")` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"test-op-id\"", @@ -595,8 +595,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| where appId !in ("test-app-id")| where clientId in ("test-client-id")` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1"}, TimeRange: timeRange, @@ -611,8 +611,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| where appId !in ("test-app-id")| where clientId in ("test-client-id")` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + @@ -625,8 +625,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| where appId !in ("test-app-id")| where clientId in ("test-client-id")` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"test-op-id\"", @@ -669,8 +669,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1"}, TimeRange: timeRange, @@ -684,8 +684,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests` + `| where (operation_Id != '' and operation_Id == '${__data.fields.traceID}') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == '${__data.fields.traceID}')` + @@ -697,8 +697,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"${__data.fields.traceID}\"", @@ -744,8 +744,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1"}, TimeRange: timeRange, @@ -759,8 +759,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + @@ -772,8 +772,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"test-op-id\"", @@ -861,8 +861,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1"}, TimeRange: timeRange, @@ -875,8 +875,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').availabilityResults,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').customEvents,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').dependencies,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').exceptions,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').pageViews,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').requests` + `| where (operation_Id != '' and operation_Id == 'op-id-multi') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'op-id-multi')` + @@ -887,8 +887,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union *,\n" + "app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').availabilityResults,\n" + @@ -937,8 +937,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1", "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r2"}, TimeRange: timeRange, @@ -951,8 +951,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').availabilityResults,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').customEvents,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').dependencies,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').exceptions,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').pageViews,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').requests` + `| where (operation_Id != '' and operation_Id == '${__data.fields.traceID}') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == '${__data.fields.traceID}')` + @@ -963,8 +963,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union *,\n" + "app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').availabilityResults,\n" + @@ -1016,8 +1016,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1", "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r2"}, TimeRange: timeRange, @@ -1030,8 +1030,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').availabilityResults,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').customEvents,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').dependencies,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').exceptions,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').pageViews,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').requests` + `| where (operation_Id != '' and operation_Id == 'op-id-multi') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'op-id-multi')` + @@ -1042,8 +1042,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union *,\n" + "app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').availabilityResults,\n" + @@ -1095,8 +1095,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1", "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r2"}, TimeRange: timeRange, @@ -1109,8 +1109,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').availabilityResults,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').customEvents,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').dependencies,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').exceptions,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').pageViews,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').requests,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r3').availabilityResults,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r3').customEvents,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r3').dependencies,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r3').exceptions,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r3').pageViews,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r3').requests` + `| where (operation_Id != '' and operation_Id == 'op-id-non-overlapping') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'op-id-non-overlapping')` + @@ -1121,8 +1121,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union *,\n" + "app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').availabilityResults,\n" + diff --git a/public/app/core/components/AppChrome/AppChrome.tsx b/public/app/core/components/AppChrome/AppChrome.tsx index 133deae94d3..9c323a7ba42 100644 --- a/public/app/core/components/AppChrome/AppChrome.tsx +++ b/public/app/core/components/AppChrome/AppChrome.tsx @@ -34,7 +34,9 @@ export function AppChrome({ children }: Props) { const dockedMenuLocalStorageState = store.getBool(DOCKED_LOCAL_STORAGE_KEY, true); const menuDockedAndOpen = !state.chromeless && state.megaMenuDocked && state.megaMenuOpen; const scopesDashboardsState = useScopesDashboardsState(); - const isScopesDashboardsOpen = Boolean(scopesDashboardsState?.isEnabled && scopesDashboardsState?.isPanelOpened); + const isScopesDashboardsOpen = Boolean( + scopesDashboardsState?.isEnabled && scopesDashboardsState?.isPanelOpened && !scopesDashboardsState?.isReadOnly + ); const isSingleTopNav = config.featureToggles.singleTopNav; useMediaQueryChange({ breakpoint: dockedMenuBreakpoint, diff --git a/public/app/core/components/AppChrome/MegaMenu/utils.test.ts b/public/app/core/components/AppChrome/MegaMenu/utils.test.ts index 0a99bffd6b3..ea5a289bd95 100644 --- a/public/app/core/components/AppChrome/MegaMenu/utils.test.ts +++ b/public/app/core/components/AppChrome/MegaMenu/utils.test.ts @@ -5,6 +5,22 @@ import { enrichHelpItem, getActiveItem, findByUrl } from './utils'; const starredDashboardUid = 'foo'; const mockNavTree: NavModelItem[] = [ + { + text: 'Bookmarks', + url: '/bookmarks', + id: 'bookmarks', + children: [ + { + text: 'Item with children', + url: '/itemWithChildren', + id: 'item-with-children', + parentItem: { + text: 'Bookmarks', + id: 'bookmarks', + }, + }, + ], + }, { text: 'Item', url: '/item', @@ -112,6 +128,10 @@ describe('getActiveItem', () => { const mockPage: NavModelItem = { text: 'Some child page', id: 'child', + parentItem: { + text: 'Item with children', + id: 'item-with-children', + }, }; expect(getActiveItem(mockNavTree, mockPage)?.id).toEqual('child'); }); diff --git a/public/app/core/components/AppChrome/MegaMenu/utils.ts b/public/app/core/components/AppChrome/MegaMenu/utils.ts index ab41e68dee6..e8864399a0c 100644 --- a/public/app/core/components/AppChrome/MegaMenu/utils.ts +++ b/public/app/core/components/AppChrome/MegaMenu/utils.ts @@ -98,7 +98,9 @@ export const getActiveItem = ( } } - if (parentItem) { + // Do not search for the parent in the bookmarks section + const isInBookmarksSection = navTree[0]?.parentItem?.id === 'bookmarks'; + if (parentItem && !isInBookmarksSection) { return getActiveItem(navTree, parentItem); } diff --git a/public/app/core/components/TimelineChart/timeline.ts b/public/app/core/components/TimelineChart/timeline.ts index f0b7eec771c..68acea164f6 100644 --- a/public/app/core/components/TimelineChart/timeline.ts +++ b/public/app/core/components/TimelineChart/timeline.ts @@ -321,7 +321,11 @@ export function getConfig(opts: TimelineCoreOptions) { continue; } - let maxChars = Math.floor(boxRect?.w / pxPerChar); + // if x placement is negative, rect is left truncated, remove it from width for calculating how many chars will display + // right truncation happens automatically + const displayedBoxWidth = boxRect.x < 0 ? boxRect?.w + boxRect.x : boxRect?.w; + + let maxChars = Math.floor(displayedBoxWidth / pxPerChar); if (showValue === VisibilityMode.Auto && maxChars < 2) { continue; @@ -333,7 +337,7 @@ export function getConfig(opts: TimelineCoreOptions) { let x = round(boxRect.x + xOff + boxRect.w / 2); if (mode === TimelineMode.Changes) { if (alignValue === 'left') { - x = round(boxRect.x + xOff + strokeWidth + textPadding); + x = round(Math.max(boxRect.x, 0) + xOff + strokeWidth + textPadding); } else if (alignValue === 'right') { x = round(boxRect.x + xOff + boxRect.w - strokeWidth - textPadding); } diff --git a/public/app/core/utils/explore.test.ts b/public/app/core/utils/explore.test.ts index 82bac2e5764..75da07f4577 100644 --- a/public/app/core/utils/explore.test.ts +++ b/public/app/core/utils/explore.test.ts @@ -1,8 +1,11 @@ -import { DataSourceApi, dateTime, ExploreUrlState, LogsSortOrder } from '@grafana/data'; +import { DataSourceApi, dateTime, ExploreUrlState, GrafanaConfig, locationUtil, LogsSortOrder } from '@grafana/data'; import { serializeStateToUrlParam } from '@grafana/data/src/utils/url'; +import { config } from '@grafana/runtime'; import { DataQuery } from '@grafana/schema'; import { RefreshPicker } from '@grafana/ui'; +import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { DEFAULT_RANGE } from 'app/features/explore/state/utils'; +import { getVariablesUrlParams } from 'app/features/variables/getAllVariableValuesForUrl'; import { DatasourceSrvMock, MockDataSourceApi } from '../../../test/mocks/datasource_srv'; @@ -152,6 +155,27 @@ describe('getExploreUrl', () => { expect(interpolateMockLoki).toBeCalled(); expect(interpolateMockProm).toBeCalled(); }); + + describe('subpath', () => { + beforeAll(() => { + locationUtil.initialize({ + config: { appSubUrl: '/subpath' } as GrafanaConfig, + getVariablesUrlParams: jest.fn(), + getTimeRangeForUrl: jest.fn(), + }); + }); + afterAll(() => { + // Reset locationUtil + locationUtil.initialize({ + config, + getTimeRangeForUrl: getTimeSrv().timeRangeForUrl, + getVariablesUrlParams: getVariablesUrlParams, + }); + }); + it('should work with sub path', async () => { + expect(await getExploreUrl(args)).toMatch(/subpath\/explore/g); + }); + }); }); describe('hasNonEmptyQuery', () => { diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts index af320c4c6ec..cb22e191a77 100644 --- a/public/app/core/utils/explore.ts +++ b/public/app/core/utils/explore.ts @@ -12,6 +12,7 @@ import { DefaultTimeZone, getNextRefId, IntervalValues, + locationUtil, LogsDedupStrategy, LogsSortOrder, rangeUtil, @@ -94,7 +95,7 @@ export async function getExploreUrl(args: GetExploreUrlArguments): Promise { ).toBeInTheDocument(); }); - it('does not show manage permissions', async () => { - renderGrafanaContactPoints(); + it('shows manage permissions and allows closing', async () => { + const { user } = renderGrafanaContactPoints(); await clickMoreActionsButton('lotsa-emails'); - expect(screen.queryByRole('menuitem', { name: /manage permissions/i })).not.toBeInTheDocument(); - }); + await user.click(await screen.findByRole('menuitem', { name: /manage permissions/i })); - describe('accesscontrol license feature enabled', () => { - testWithLicenseFeatures(['accesscontrol']); + const permissionsDialog = await screen.findByRole('dialog', { name: /drawer title manage permissions/i }); - it('shows manage permissions and allows closing', async () => { - const { user } = renderGrafanaContactPoints(); + expect(permissionsDialog).toBeInTheDocument(); + expect(await screen.findByRole('table')).toBeInTheDocument(); - await clickMoreActionsButton('lotsa-emails'); - - await user.click(await screen.findByRole('menuitem', { name: /manage permissions/i })); - - const permissionsDialog = await screen.findByRole('dialog', { name: /drawer title manage permissions/i }); - - expect(permissionsDialog).toBeInTheDocument(); - expect(await screen.findByRole('table')).toBeInTheDocument(); - - await user.click(within(permissionsDialog).getAllByRole('button', { name: /close/i })[0]); - expect(permissionsDialog).not.toBeInTheDocument(); - }); + await user.click(within(permissionsDialog).getAllByRole('button', { name: /close/i })[0]); + expect(permissionsDialog).not.toBeInTheDocument(); }); }); }); diff --git a/public/app/features/alerting/unified/components/contact-points/utils.ts b/public/app/features/alerting/unified/components/contact-points/utils.ts index 0f81ff496a9..5f6deb9c0b0 100644 --- a/public/app/features/alerting/unified/components/contact-points/utils.ts +++ b/public/app/features/alerting/unified/components/contact-points/utils.ts @@ -2,7 +2,6 @@ import { difference, groupBy, take, trim, upperFirst } from 'lodash'; import { ReactNode } from 'react'; import { config } from '@grafana/runtime'; -import { contextSrv } from 'app/core/core'; import { canAdminEntity, shouldUseK8sApi } from 'app/features/alerting/unified/utils/k8s/utils'; import { AlertManagerCortexConfig, @@ -210,4 +209,4 @@ function getNotifierMetadata(notifiers: NotifierDTO[], receiver: GrafanaManagedR } export const showManageContactPointPermissions = (alertmanager: string, contactPoint: GrafanaManagedContactPoint) => - shouldUseK8sApi(alertmanager) && contextSrv.licensedAccessControlEnabled() && canAdminEntity(contactPoint); + shouldUseK8sApi(alertmanager) && canAdminEntity(contactPoint); diff --git a/public/app/features/alerting/unified/components/notification-policies/Policy.test.tsx b/public/app/features/alerting/unified/components/notification-policies/Policy.test.tsx index dfff88a463a..1e3a0af1583 100644 --- a/public/app/features/alerting/unified/components/notification-policies/Policy.test.tsx +++ b/public/app/features/alerting/unified/components/notification-policies/Policy.test.tsx @@ -1,10 +1,10 @@ -import { render, renderHook, screen, within } from '@testing-library/react'; +import { renderHook, screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { first, noop } from 'lodash'; -import { Router } from 'react-router-dom'; -import { CompatRouter } from 'react-router-dom-v5-compat'; +import { Routes, Route } from 'react-router-dom-v5-compat'; +import { render } from 'test/test-utils'; -import { config, locationService } from '@grafana/runtime'; +import { config } from '@grafana/runtime'; import { contextSrv } from 'app/core/core'; import { AlertmanagerGroup, @@ -345,13 +345,17 @@ describe('Policy', () => { }); }); +// Doesn't matter which path the routes use, it just needs to match the initialEntries history entry to render the element const renderPolicy = (element: JSX.Element) => render( - - - {element} - - + + {element}} /> + , + { + historyOptions: { + initialEntries: ['/'], + }, + } ); const eq = MatcherOperator.equal; diff --git a/public/app/features/alerting/unified/components/receivers/ReceiversSection.tsx b/public/app/features/alerting/unified/components/receivers/ReceiversSection.tsx index a8c9a2591e8..5b3fb521e8c 100644 --- a/public/app/features/alerting/unified/components/receivers/ReceiversSection.tsx +++ b/public/app/features/alerting/unified/components/receivers/ReceiversSection.tsx @@ -1,6 +1,6 @@ import { css, cx } from '@emotion/css'; import * as React from 'react'; -import { Link } from 'react-router-dom'; +import { Link } from 'react-router-dom-v5-compat'; import { useToggle } from 'react-use'; import { GrafanaTheme2 } from '@grafana/data'; 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 8805b40cf1e..57c385d856e 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 @@ -1,7 +1,7 @@ import { css } from '@emotion/css'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { FormProvider, SubmitErrorHandler, UseFormWatch, useForm } from 'react-hook-form'; -import { useParams } from 'react-router-dom'; +import { useParams } from 'react-router-dom-v5-compat'; import { GrafanaTheme2 } from '@grafana/data'; import { config, locationService } from '@grafana/runtime'; @@ -82,7 +82,7 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => { const routeParams = useParams<{ type: string; id: string }>(); const ruleType = translateRouteParamToRuleType(routeParams.type); - const uidFromParams = routeParams.id; + const uidFromParams = routeParams.id || ''; const [showDeleteModal, setShowDeleteModal] = useState(false); diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx index 70dbaf94fdf..c78fb2df968 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx @@ -1,5 +1,5 @@ import { ReactNode } from 'react'; -import { Route } from 'react-router-dom'; +import { Routes, Route } from 'react-router-dom-v5-compat'; import { ui } from 'test/helpers/alertingRuleEditor'; import { clickSelectOption } from 'test/helpers/selectOptionInTest'; import { render, screen, waitForElementToBeRemoved, userEvent } from 'test/test-utils'; @@ -157,7 +157,10 @@ describe('Can create a new grafana managed alert using simplified routing', () = function renderSimplifiedRuleEditor() { return render( - + + } /> + } /> + , { historyOptions: { initialEntries: ['/alerting/new/alerting'] } } ); diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/SimpleCondition.tsx b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/SimpleCondition.tsx index 7307675defd..1e745c46da6 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/SimpleCondition.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/SimpleCondition.tsx @@ -218,11 +218,17 @@ export function getSimpleConditionFromExpressions(expressions: Array query.model.type === ExpressionQueryType.threshold && query.refId === SIMPLE_CONDITION_THRESHOLD_ID ); const conditionsFromThreshold = thresholdExpression?.model.conditions ?? []; + const whenField = reduceExpression?.model.reducer ?? ReducerID.last; + const params = conditionsFromThreshold[0]?.evaluator?.params + ? [...conditionsFromThreshold[0]?.evaluator?.params] + : [0]; + const type = conditionsFromThreshold[0]?.evaluator?.type ?? EvalFunction.IsAbove; + return { - whenField: reduceExpression?.model.reducer ?? ReducerID.last, + whenField: whenField, evaluator: { - params: [...conditionsFromThreshold[0]?.evaluator?.params] ?? [0], - type: conditionsFromThreshold[0]?.evaluator?.type ?? EvalFunction.IsAbove, + params: params, + type: type, }, }; } diff --git a/public/app/features/alerting/unified/components/rules/CloneRule.tsx b/public/app/features/alerting/unified/components/rules/CloneRule.tsx index cb0d0cdbc80..4e12142a156 100644 --- a/public/app/features/alerting/unified/components/rules/CloneRule.tsx +++ b/public/app/features/alerting/unified/components/rules/CloneRule.tsx @@ -1,6 +1,5 @@ import { forwardRef, useState } from 'react'; -import { useLocation } from 'react-router-dom'; -import { Navigate } from 'react-router-dom-v5-compat'; +import { Navigate, useLocation } from 'react-router-dom-v5-compat'; import { Button, ConfirmModal } from '@grafana/ui'; import { RuleIdentifier } from 'app/types/unified-alerting'; diff --git a/public/app/features/alerting/unified/state/AlertmanagerContext.test.tsx b/public/app/features/alerting/unified/state/AlertmanagerContext.test.tsx index 9a4ab2346f4..a6b007f507e 100644 --- a/public/app/features/alerting/unified/state/AlertmanagerContext.test.tsx +++ b/public/app/features/alerting/unified/state/AlertmanagerContext.test.tsx @@ -1,9 +1,7 @@ import { renderHook } from '@testing-library/react'; -import { createMemoryHistory } from 'history'; import * as React from 'react'; -import { MemoryRouter, Router } from 'react-router-dom'; -import { CompatRouter } from 'react-router-dom-v5-compat'; +import { locationService } from '@grafana/runtime'; import store from 'app/core/store'; import { AlertManagerImplementation } from 'app/plugins/datasource/alertmanager/types'; @@ -34,11 +32,7 @@ describe('useAlertmanager', () => { .spyOn(useAlertManagerSources, 'useAlertManagersByPermission') .mockReturnValueOnce({ availableExternalDataSources: [], availableInternalDataSources: [] }); const wrapper = ({ children }: React.PropsWithChildren) => ( - - - {children} - - + {children} ); const { result } = renderHook(() => useAlertmanager(), { wrapper }); @@ -52,11 +46,7 @@ describe('useAlertmanager', () => { }); const wrapper = ({ children }: React.PropsWithChildren) => ( - - - {children} - - + {children} ); const { result } = renderHook(() => useAlertmanager(), { wrapper }); @@ -68,15 +58,10 @@ describe('useAlertmanager', () => { .spyOn(useAlertManagerSources, 'useAlertManagersByPermission') .mockReturnValueOnce({ availableExternalDataSources: [externalAmProm], availableInternalDataSources: [] }); - const history = createMemoryHistory(); - history.push({ search: `alertmanager=${externalAmProm.name}` }); + locationService.push({ search: `alertmanager=${externalAmProm.name}` }); const wrapper = ({ children }: React.PropsWithChildren) => ( - - - {children} - - + {children} ); const { result } = renderHook(() => useAlertmanager(), { wrapper }); @@ -88,15 +73,10 @@ describe('useAlertmanager', () => { .spyOn(useAlertManagerSources, 'useAlertManagersByPermission') .mockReturnValueOnce({ availableExternalDataSources: [], availableInternalDataSources: [] }); - const history = createMemoryHistory(); - history.push({ search: `alertmanager=Not available external AM` }); + locationService.push({ search: `alertmanager=Not available external AM` }); const wrapper = ({ children }: React.PropsWithChildren) => ( - - - {children} - - + {children} ); const { result } = renderHook(() => useAlertmanager(), { wrapper }); @@ -109,15 +89,11 @@ describe('useAlertmanager', () => { .mockReturnValueOnce({ availableExternalDataSources: [externalAmProm], availableInternalDataSources: [] }); const wrapper = ({ children }: React.PropsWithChildren) => ( - - - {children} - - + {children} ); store.set(ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, externalAmProm.name); - + locationService.push({ search: '' }); const { result } = renderHook(() => useAlertmanager(), { wrapper }); expect(result.current.selectedAlertmanager).toBe(externalAmProm.name); }); @@ -128,15 +104,10 @@ describe('useAlertmanager', () => { availableInternalDataSources: [], }); - const history = createMemoryHistory(); - history.push({ search: `alertmanager=${externalAmProm.name}` }); + locationService.push({ search: `alertmanager=${externalAmProm.name}` }); const wrapper = ({ children }: React.PropsWithChildren) => ( - - - {children} - - + {children} ); store.set(ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, externalAmMimir.name); diff --git a/public/app/features/alerting/unified/state/AlertmanagerContext.tsx b/public/app/features/alerting/unified/state/AlertmanagerContext.tsx index f2d6e774ce8..d196dd7b565 100644 --- a/public/app/features/alerting/unified/state/AlertmanagerContext.tsx +++ b/public/app/features/alerting/unified/state/AlertmanagerContext.tsx @@ -1,10 +1,10 @@ import * as React from 'react'; +import { locationService } from '@grafana/runtime'; import store from 'app/core/store'; import { AlertManagerDataSourceJsonData, AlertManagerImplementation } from 'app/plugins/datasource/alertmanager/types'; import { useAlertManagersByPermission } from '../hooks/useAlertManagerSources'; -import { useURLSearchParams } from '../hooks/useURLSearchParams'; import { ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, ALERTMANAGER_NAME_QUERY_KEY } from '../utils/constants'; import { AlertManagerDataSource, @@ -30,7 +30,8 @@ interface Props extends React.PropsWithChildren { } const AlertmanagerProvider = ({ children, accessType, alertmanagerSourceName }: Props) => { - const [queryParams, updateQueryParams] = useURLSearchParams(); + const queryParams = locationService.getSearch(); + const updateQueryParams = locationService.partial; const allAvailableAlertManagers = useAlertManagersByPermission(accessType); const availableAlertManagers = allAvailableAlertManagers.availableInternalDataSources.concat( allAvailableAlertManagers.availableExternalDataSources diff --git a/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx b/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx index 6d8d2b14e26..cb56a761ab9 100644 --- a/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx +++ b/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx @@ -121,7 +121,7 @@ const BrowseDashboardsPage = memo(() => { onEditTitle={showEditTitle ? onEditTitle : undefined} actions={ <> - {config.featureToggles.dashboardRestore && config.featureToggles.dashboardRestoreUI && hasAdminRights && ( + {config.featureToggles.dashboardRestore && hasAdminRights && (

diff --git a/public/app/features/dashboard-scene/scene/DashboardControls.test.tsx b/public/app/features/dashboard-scene/scene/DashboardControls.test.tsx index aefb2953e2b..34c5f3fe057 100644 --- a/public/app/features/dashboard-scene/scene/DashboardControls.test.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardControls.test.tsx @@ -1,22 +1,40 @@ -import { render } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; +import { toUtc } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { SceneDataLayerControls, SceneVariableSet, TextBoxVariable, VariableValueSelectors } from '@grafana/scenes'; import { DashboardControls, DashboardControlsState } from './DashboardControls'; import { DashboardScene } from './DashboardScene'; +const mockGetAnchorInfo = jest.fn((link) => ({ + href: `/dashboard/${link.title}`, + title: link.title, + tooltip: link.tooltip || null, +})); + +// Mock the getLinkSrv function +jest.mock('app/features/panel/panellinks/link_srv', () => ({ + getLinkSrv: jest.fn(() => ({ + getAnchorInfo: mockGetAnchorInfo, + })), +})); + describe('DashboardControls', () => { describe('Given a standard scene', () => { it('should initialize with default values', () => { - const scene = buildTestScene(); + const { controls: scene } = buildTestScene(); expect(scene.state.variableControls).toEqual([]); expect(scene.state.timePicker).toBeDefined(); expect(scene.state.refreshPicker).toBeDefined(); }); it('should return if time controls are hidden', () => { - const scene = buildTestScene({ hideTimeControls: false, hideVariableControls: false, hideLinksControls: false }); + const { controls: scene } = buildTestScene({ + hideTimeControls: false, + hideVariableControls: false, + hideLinksControls: false, + }); expect(scene.hasControls()).toBeTruthy(); scene.setState({ hideTimeControls: true }); expect(scene.hasControls()).toBeTruthy(); @@ -27,14 +45,14 @@ describe('DashboardControls', () => { describe('Component', () => { it('should render', () => { - const scene = buildTestScene(); + const { controls: scene } = buildTestScene(); expect(() => { render(); }).not.toThrow(); }); it('should render visible controls', async () => { - const scene = buildTestScene({ + const { controls: scene } = buildTestScene({ variableControls: [new VariableValueSelectors({}), new SceneDataLayerControls()], }); const renderer = render(); @@ -47,7 +65,7 @@ describe('DashboardControls', () => { }); it('should render with hidden controls', async () => { - const scene = buildTestScene({ + const { controls: scene } = buildTestScene({ hideTimeControls: true, hideVariableControls: true, hideLinksControls: true, @@ -61,13 +79,13 @@ describe('DashboardControls', () => { describe('UrlSync', () => { it('should return keys', () => { - const scene = buildTestScene(); + const { controls: scene } = buildTestScene(); // @ts-expect-error expect(scene._urlSync.getKeys()).toEqual(['_dash.hideTimePicker', '_dash.hideVariables', '_dash.hideLinks']); }); it('should not return url state for hide flags', () => { - const scene = buildTestScene(); + const { controls: scene } = buildTestScene(); expect(scene.getUrlState()).toEqual({}); scene.setState({ hideTimeControls: true, @@ -78,7 +96,7 @@ describe('DashboardControls', () => { }); it('should update from url', () => { - const scene = buildTestScene(); + const { controls: scene } = buildTestScene(); scene.updateFromUrl({ '_dash.hideTimePicker': 'true', '_dash.hideVariables': 'true', @@ -98,7 +116,11 @@ describe('DashboardControls', () => { }); it('should not override state if no new state comes from url', () => { - const scene = buildTestScene({ hideTimeControls: true, hideVariableControls: true, hideLinksControls: true }); + const { controls: scene } = buildTestScene({ + hideTimeControls: true, + hideVariableControls: true, + hideLinksControls: true, + }); scene.updateFromUrl({}); expect(scene.state.hideTimeControls).toBeTruthy(); expect(scene.state.hideVariableControls).toBeTruthy(); @@ -106,7 +128,11 @@ describe('DashboardControls', () => { }); it('should not call setState if no changes', () => { - const scene = buildTestScene({ hideTimeControls: true, hideVariableControls: true, hideLinksControls: true }); + const { controls: scene } = buildTestScene({ + hideTimeControls: true, + hideVariableControls: true, + hideLinksControls: true, + }); const setState = jest.spyOn(scene, 'setState'); scene.updateFromUrl({ @@ -118,9 +144,34 @@ describe('DashboardControls', () => { expect(setState).toHaveBeenCalledTimes(0); }); }); + + it('Should update link hrefs when time range changes', () => { + const { controls, dashboard } = buildTestScene(); + render(); + + //clear initial calls to getAnchorInfo + mockGetAnchorInfo.mockClear(); + + act(() => { + // Update time range + dashboard.state.$timeRange?.setState({ + value: { + from: toUtc('2021-01-01'), + to: toUtc('2021-01-02'), + raw: { from: toUtc('2020-01-01'), to: toUtc('2020-01-02') }, + }, + }); + }); + + //expect getAnchorInfo to be called after time range change + expect(mockGetAnchorInfo).toHaveBeenCalledTimes(1); + }); }); -function buildTestScene(state?: Partial): DashboardControls { +function buildTestScene(state?: Partial): { + dashboard: DashboardScene; + controls: DashboardControls; +} { const variable = new TextBoxVariable({ name: 'A', label: 'A', @@ -155,5 +206,5 @@ function buildTestScene(state?: Partial): DashboardContr dashboard.activate(); variable.activate(); - return dashboard.state.controls as DashboardControls; + return { dashboard, controls: dashboard.state.controls as DashboardControls }; } diff --git a/public/app/features/dashboard-scene/scene/DashboardControls.tsx b/public/app/features/dashboard-scene/scene/DashboardControls.tsx index 3805792f93b..41e125957a5 100644 --- a/public/app/features/dashboard-scene/scene/DashboardControls.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardControls.tsx @@ -122,9 +122,10 @@ function DashboardControlsRenderer({ model }: SceneComponentProps { if (!result.destination || !result.source) { return; } + reportInteraction('Variable drag and drop'); onChangeOrder(result.source.index, result.destination.index); }; - return ( -

-
- {variables.length === 0 && } - - {variables.length > 0 && ( - -
- - - - - - - - - - {(provided) => ( - - {variables.map((variableScene, index) => { - const variableState = variableScene.state; - return ( - - ); - })} - {provided.placeholder} - - )} - - -
VariableDefinition -
-
- - - - -
- )} -
-
+ return variables.length <= 0 ? ( + + ) : ( + + + + + + + + + + + {(provided) => ( + + {variables.map((variableScene, index) => { + const variableState = variableScene.state; + return ( + + ); + })} + {provided.placeholder} + + )} + + +
VariableDefinition +
+ + + + +
); } -function EmptyVariablesList({ onAdd }: { onAdd: () => void }): ReactElement { +function EmptyVariablesList({ onAdd }: { onAdd: () => void }) { return ( void }): ReactElement { const getStyles = () => ({ tableContainer: css({ - overflow: 'scroll', - width: '100%', + overflow: 'auto', }), }); diff --git a/public/app/features/dashboard/components/DeleteDashboard/DeleteDashboardModal.tsx b/public/app/features/dashboard/components/DeleteDashboard/DeleteDashboardModal.tsx index d82f4fd56a0..2d777ba0bc2 100644 --- a/public/app/features/dashboard/components/DeleteDashboard/DeleteDashboardModal.tsx +++ b/public/app/features/dashboard/components/DeleteDashboard/DeleteDashboardModal.tsx @@ -34,7 +34,7 @@ const DeleteDashboardModalUnconnected = ({ hideModal, cleanUpDashboardAndVariabl dashboard: 1, }, source: 'dashboard_settings', - restore_enabled: config.featureToggles.dashboardRestoreUI, + restore_enabled: config.featureToggles.dashboardRestore, }); await deleteItems({ selectedItems: { diff --git a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx index 2a5941c82e9..d65ad7a9cb0 100644 --- a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx @@ -31,7 +31,7 @@ export const SaveDashboardErrorProxy = ({ setErrorIsHandled, }: SaveDashboardErrorProxyProps) => { const { onDashboardSave } = useDashboardSave(); - const isRestoreDashboardsEnabled = config.featureToggles.dashboardRestore && config.featureToggles.dashboardRestoreUI; + const isRestoreDashboardsEnabled = config.featureToggles.dashboardRestore; return ( <> {error.data && error.data.status === 'version-mismatch' && ( diff --git a/public/app/features/explore/ExplorePage.tsx b/public/app/features/explore/ExplorePage.tsx index 1ab96c05414..f5f4c9b6ccc 100644 --- a/public/app/features/explore/ExplorePage.tsx +++ b/public/app/features/explore/ExplorePage.tsx @@ -1,10 +1,11 @@ import { css, cx } from '@emotion/css'; import { useEffect, useState } from 'react'; +import { useLocalStorage } from 'react-use'; import { CoreApp, GrafanaTheme2 } from '@grafana/data'; import { config } from '@grafana/runtime'; import { DataQuery } from '@grafana/schema/dist/esm/index'; -import { ErrorBoundaryAlert, Modal, useStyles2, useTheme2 } from '@grafana/ui'; +import { Badge, ErrorBoundaryAlert, Modal, useStyles2, useTheme2 } from '@grafana/ui'; import { QueryOperationAction } from 'app/core/components/QueryOperationRow/QueryOperationAction'; import { SplitPaneWrapper } from 'app/core/components/SplitPaneWrapper/SplitPaneWrapper'; import { useGrafana } from 'app/core/context/GrafanaContext'; @@ -21,6 +22,7 @@ import { ExploreActions } from './ExploreActions'; import { ExploreDrawer } from './ExploreDrawer'; import { ExplorePaneContainer } from './ExplorePaneContainer'; import { QueriesDrawerContextProvider, useQueriesDrawerContext } from './QueriesDrawer/QueriesDrawerContext'; +import { QUERY_LIBRARY_LOCAL_STORAGE_KEYS } from './QueryLibrary/QueryLibrary'; import { queryLibraryTrackAddFromQueryRow } from './QueryLibrary/QueryLibraryAnalyticsEvents'; import { QueryTemplateForm } from './QueryLibrary/QueryTemplateForm'; import RichHistoryContainer from './RichHistory/RichHistoryContainer'; @@ -63,6 +65,10 @@ function ExplorePageContent(props: GrafanaRouteComponentProps<{}, ExploreQueryPa const { drawerOpened, setDrawerOpened, queryLibraryAvailable } = useQueriesDrawerContext(); const showCorrelationEditorBar = config.featureToggles.correlations && (correlationDetails?.editorMode || false); const [queryToAdd, setQueryToAdd] = useState(); + const [showQueryLibraryBadgeButton, setShowQueryLibraryBadgeButton] = useLocalStorage( + QUERY_LIBRARY_LOCAL_STORAGE_KEYS.explore.newButton, + true + ); useEffect(() => { //This is needed for breadcrumbs and topnav. @@ -77,19 +83,32 @@ function ExplorePageContent(props: GrafanaRouteComponentProps<{}, ExploreQueryPa if (hasQueryLibrary) { RowActionComponents.addKeyedExtraRenderAction(QUERY_LIBRARY_ACTION_KEY, { scope: CoreApp.Explore, - queryActionComponent: (props) => ( - { - setQueryToAdd(props.query); - }} - /> - ), + queryActionComponent: (props) => + showQueryLibraryBadgeButton ? ( + { + setQueryToAdd(props.query); + setShowQueryLibraryBadgeButton(false); + }} + style={{ cursor: 'pointer' }} + /> + ) : ( + { + setQueryToAdd(props.query); + }} + /> + ), }); } - }, []); + }, [showQueryLibraryBadgeButton, setShowQueryLibraryBadgeButton]); useKeyboardShortcuts(); diff --git a/public/app/features/explore/QueryLibrary/QueryLibrary.tsx b/public/app/features/explore/QueryLibrary/QueryLibrary.tsx index 9a296b41fbb..fffc454ee3a 100644 --- a/public/app/features/explore/QueryLibrary/QueryLibrary.tsx +++ b/public/app/features/explore/QueryLibrary/QueryLibrary.tsx @@ -1,3 +1,6 @@ +import { useLocalStorage } from 'react-use'; + +import { QueryLibraryExpmInfo } from './QueryLibraryExpmInfo'; import { QueryTemplatesList } from './QueryTemplatesList'; export interface QueryLibraryProps { @@ -6,6 +9,26 @@ export interface QueryLibraryProps { activeDatasources?: string[]; } +export const QUERY_LIBRARY_LOCAL_STORAGE_KEYS = { + explore: { + notifyUserAboutQueryLibrary: 'grafana.explore.query-library.notifyUserAboutQueryLibrary', + newButton: 'grafana.explore.query-library.newButton', + }, +}; + export function QueryLibrary({ activeDatasources }: QueryLibraryProps) { - return ; + const [notifyUserAboutQueryLibrary, setNotifyUserAboutQueryLibrary] = useLocalStorage( + QUERY_LIBRARY_LOCAL_STORAGE_KEYS.explore.notifyUserAboutQueryLibrary, + true + ); + + return ( + <> + setNotifyUserAboutQueryLibrary(false)} + /> + + + ); } diff --git a/public/app/features/explore/QueryLibrary/QueryLibraryExpmInfo.tsx b/public/app/features/explore/QueryLibrary/QueryLibraryExpmInfo.tsx new file mode 100644 index 00000000000..7c88af4a555 --- /dev/null +++ b/public/app/features/explore/QueryLibrary/QueryLibraryExpmInfo.tsx @@ -0,0 +1,23 @@ +import { Alert, Modal } from '@grafana/ui'; + +interface Props { + isOpen: boolean; + onDismiss: () => void; +} + +export function QueryLibraryExpmInfo({ isOpen, onDismiss }: Props) { + return ( + + + + + + ); +} diff --git a/public/app/features/explore/QueryLibrary/QueryTemplateForm.tsx b/public/app/features/explore/QueryLibrary/QueryTemplateForm.tsx index ed654fa378d..eefab87c7d1 100644 --- a/public/app/features/explore/QueryLibrary/QueryTemplateForm.tsx +++ b/public/app/features/explore/QueryLibrary/QueryTemplateForm.tsx @@ -66,9 +66,7 @@ export const QueryTemplateForm = ({ onCancel, onSave, queryToAdd, templateData } .then(() => { getAppEvents().publish({ type: AppEvents.alertSuccess.name, - payload: [ - t('explore.query-library.query-template-added', 'Query template successfully added to the library'), - ], + payload: [t('explore.query-library.query-template-added', 'Query successfully saved to the library')], }); return true; }) @@ -76,7 +74,7 @@ export const QueryTemplateForm = ({ onCancel, onSave, queryToAdd, templateData } getAppEvents().publish({ type: AppEvents.alertError.name, payload: [ - t('explore.query-library.query-template-add-error', 'Error attempting to add this query to the library'), + t('explore.query-library.query-template-add-error', 'Error attempting to save this query to the library'), ], }); return false; diff --git a/public/app/features/explore/QueryLibrary/QueryTemplatesList.tsx b/public/app/features/explore/QueryLibrary/QueryTemplatesList.tsx index ead35b647c6..48fd253e0f1 100644 --- a/public/app/features/explore/QueryLibrary/QueryTemplatesList.tsx +++ b/public/app/features/explore/QueryLibrary/QueryTemplatesList.tsx @@ -4,7 +4,7 @@ import { useEffect, useMemo, useState } from 'react'; import { AppEvents, GrafanaTheme2, SelectableValue } from '@grafana/data'; import { getAppEvents, getDataSourceSrv } from '@grafana/runtime'; -import { EmptyState, FilterInput, InlineLabel, MultiSelect, Spinner, useStyles2, Stack } from '@grafana/ui'; +import { EmptyState, FilterInput, InlineLabel, MultiSelect, Spinner, useStyles2, Stack, Badge } from '@grafana/ui'; import { t, Trans } from 'app/core/internationalization'; import { createQueryText } from 'app/core/utils/richHistory'; import { useAllQueryTemplatesQuery } from 'app/features/query-library'; @@ -15,6 +15,7 @@ import { getDatasourceSrv } from '../../plugins/datasource_srv'; import { QueryLibraryProps } from './QueryLibrary'; import { queryLibraryTrackFilterDatasource } from './QueryLibraryAnalyticsEvents'; +import { QueryLibraryExpmInfo } from './QueryLibraryExpmInfo'; import QueryTemplatesTable from './QueryTemplatesTable'; import { QueryTemplateRow } from './QueryTemplatesTable/types'; import { searchQueryLibrary } from './utils/search'; @@ -23,6 +24,7 @@ interface QueryTemplatesListProps extends QueryLibraryProps {} export function QueryTemplatesList(props: QueryTemplatesListProps) { const { data, isLoading, error } = useAllQueryTemplatesQuery(); + const [isModalOpen, setIsModalOpen] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [datasourceFilters, setDatasourceFilters] = useState>>( props.activeDatasources?.map((ds) => ({ value: ds, label: ds })) || [] @@ -163,6 +165,7 @@ export function QueryTemplatesList(props: QueryTemplatesListProps) { return ( <> + setIsModalOpen(false)} /> + setIsModalOpen(true)} + /> diff --git a/public/app/features/explore/RichHistory/RichHistory.tsx b/public/app/features/explore/RichHistory/RichHistory.tsx index cb00212d199..7ddb1d05c90 100644 --- a/public/app/features/explore/RichHistory/RichHistory.tsx +++ b/public/app/features/explore/RichHistory/RichHistory.tsx @@ -11,6 +11,7 @@ import { RichHistorySettings, createDatasourcesList, } from 'app/core/utils/richHistory'; +import { QUERY_LIBRARY_GET_LIMIT, queryLibraryApi } from 'app/features/query-library/api/factory'; import { useSelector } from 'app/types'; import { RichHistoryQuery } from 'app/types/explore'; @@ -96,8 +97,10 @@ export function RichHistory(props: RichHistoryProps) { .map((eDs) => listOfDatasources.find((ds) => ds.uid === eDs.datasource?.uid)?.name) .filter((name): name is string => !!name); + const queryTemplatesCount = useSelector(queryLibraryApi.endpoints.allQueryTemplates.select()).data?.length || 0; + const QueryLibraryTab: TabConfig = { - label: i18n.queryLibrary, + label: `${i18n.queryLibrary} (${queryTemplatesCount}/${QUERY_LIBRARY_GET_LIMIT})`, value: Tabs.QueryLibrary, content: , icon: 'book', diff --git a/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx b/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx index 3c30c6eae7c..cbbcac5f6fe 100644 --- a/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import { DataQuery } from '@grafana/schema'; import { Button, Modal } from '@grafana/ui'; -import { isQueryLibraryEnabled } from 'app/features/query-library'; +import { isQueryLibraryEnabled, useAllQueryTemplatesQuery } from 'app/features/query-library'; import { queryLibraryTrackAddFromQueryHistory, @@ -16,6 +16,7 @@ type Props = { }; export const RichHistoryAddToLibrary = ({ query }: Props) => { + const { refetch } = useAllQueryTemplatesQuery(); const [isOpen, setIsOpen] = useState(false); const [hasBeenSaved, setHasBeenSaved] = useState(false); @@ -45,6 +46,7 @@ export const RichHistoryAddToLibrary = ({ query }: Props) => { if (isSuccess) { setIsOpen(false); setHasBeenSaved(true); + refetch(); queryLibraryTrackAddFromQueryHistory(query.datasource?.type || ''); } }} diff --git a/public/app/features/explore/extensions/getExploreExtensionConfigs.tsx b/public/app/features/explore/extensions/getExploreExtensionConfigs.tsx index d842e451aa0..887e446c36d 100644 --- a/public/app/features/explore/extensions/getExploreExtensionConfigs.tsx +++ b/public/app/features/explore/extensions/getExploreExtensionConfigs.tsx @@ -3,7 +3,8 @@ import { contextSrv } from 'app/core/core'; import { dispatch } from 'app/store/store'; import { AccessControlAction } from 'app/types'; -import { createAddedLinkConfig, logWarning } from '../../plugins/extensions/utils'; +import { log } from '../../plugins/extensions/logs/log'; +import { createAddedLinkConfig } from '../../plugins/extensions/utils'; import { changeCorrelationEditorDetails } from '../state/main'; import { runQueries } from '../state/query'; @@ -54,7 +55,7 @@ export function getExploreExtensionConfigs(): PluginExtensionAddedLinkConfig[] { }), ]; } catch (error) { - logWarning(`Could not configure extensions for Explore due to: "${error}"`); + log.warning(`Could not configure extensions for Explore due to: "${error}"`); return []; } } diff --git a/public/app/features/explore/spec/queryLibrary.test.tsx b/public/app/features/explore/spec/queryLibrary.test.tsx index d69cc001f16..f2422f450d7 100644 --- a/public/app/features/explore/spec/queryLibrary.test.tsx +++ b/public/app/features/explore/spec/queryLibrary.test.tsx @@ -137,7 +137,7 @@ describe('QueryLibrary', () => { expect(testEventBus.publish).toHaveBeenCalledWith( expect.objectContaining({ type: 'alert-success', - payload: ['Query template successfully added to the library'], + payload: ['Query successfully saved to the library'], }) ); await assertAddToQueryLibraryButtonExists(false); diff --git a/public/app/features/gops/configuration-tracker/alerting/hooks.ts b/public/app/features/gops/configuration-tracker/alerting/hooks.ts index 3dbc9844d18..f0cde843803 100644 --- a/public/app/features/gops/configuration-tracker/alerting/hooks.ts +++ b/public/app/features/gops/configuration-tracker/alerting/hooks.ts @@ -35,20 +35,9 @@ export function isOnCallContactPointReady(contactPoints: Receiver[]) { ); } -export function useGetContactPoints() { - const alertmanagerConfiguration = alertmanagerApi.endpoints.getAlertmanagerConfiguration.useQuery( - GRAFANA_RULES_SOURCE_NAME, - { - refetchOnFocus: true, - refetchOnReconnect: true, - refetchOnMountOrArgChange: true, - } - ); - - const contactPoints = alertmanagerConfiguration.data?.alertmanager_config?.receivers ?? []; - return { contactPoints, isLoading: alertmanagerConfiguration.isLoading }; -} - +/** + * @deprecated Will be removed when notification policies is moved to k8s API. Do not use! + */ export function useGetDefaultContactPoint() { const alertmanagerConfiguration = alertmanagerApi.endpoints.getAlertmanagerConfiguration.useQuery( GRAFANA_RULES_SOURCE_NAME, diff --git a/public/app/features/gops/configuration-tracker/irmHooks.ts b/public/app/features/gops/configuration-tracker/irmHooks.ts index a18bc7fd35c..39121db0e6e 100644 --- a/public/app/features/gops/configuration-tracker/irmHooks.ts +++ b/public/app/features/gops/configuration-tracker/irmHooks.ts @@ -1,14 +1,10 @@ import { useMemo } from 'react'; import { locationService } from '@grafana/runtime'; +import { useGrafanaContactPoints } from 'app/features/alerting/unified/components/contact-points/useContactPoints'; import { RelativeUrl, createRelativeUrl } from 'app/features/alerting/unified/utils/url'; -import { - isOnCallContactPointReady, - useGetContactPoints, - useGetDefaultContactPoint, - useIsCreateAlertRuleDone, -} from './alerting/hooks'; +import { isOnCallContactPointReady, useGetDefaultContactPoint, useIsCreateAlertRuleDone } from './alerting/hooks'; import { isContactPointReady } from './alerting/utils'; import { ConfigurationStepsEnum, DataSourceConfigurationData, IrmCardConfiguration } from './components/ConfigureIRM'; import { useGetIncidentPluginConfig } from './incidents/hooks'; @@ -53,7 +49,8 @@ export interface EssentialsConfigurationData { function useGetConfigurationForApps() { // configuration checks for alerting - const { contactPoints, isLoading: isLoadingContactPoints } = useGetContactPoints(); + const { contactPoints, isLoading: isLoadingContactPoints } = useGrafanaContactPoints(); + // TODO: Switch to k8s API/refactored notification policies hook when available const { defaultContactpoint, isLoading: isLoadingDefaultContactPoint } = useGetDefaultContactPoint(); const { isDone: isCreateAlertRuleDone, isLoading: isLoadingAlertCreatedDone } = useIsCreateAlertRuleDone(); // configuration checks for incidents @@ -132,8 +129,8 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData { button: { type: 'openLink', urlLink: { - url: `/alerting/notifications/receivers/${defaultContactpoint}/edit`, - queryParams: { alertmanager: 'grafana' }, + url: `/alerting/notifications`, + queryParams: { search: defaultContactpoint, alertmanager: 'grafana' }, }, label: 'Edit', labelOnDone: 'View', diff --git a/public/app/features/plugins/extensions/getPluginExtensions.test.tsx b/public/app/features/plugins/extensions/getPluginExtensions.test.tsx index 23d44c6f540..925b0cfbd7b 100644 --- a/public/app/features/plugins/extensions/getPluginExtensions.test.tsx +++ b/public/app/features/plugins/extensions/getPluginExtensions.test.tsx @@ -4,6 +4,8 @@ import { PluginExtensionAddedComponentConfig, PluginExtensionAddedLinkConfig } f import { reportInteraction } from '@grafana/runtime'; import { getPluginExtensions } from './getPluginExtensions'; +import { log } from './logs/log'; +import { resetLogMock } from './logs/testUtils'; import { AddedComponentsRegistry } from './registry/AddedComponentsRegistry'; import { AddedLinksRegistry } from './registry/AddedLinksRegistry'; import { isReadOnlyProxy } from './utils'; @@ -16,6 +18,16 @@ jest.mock('@grafana/runtime', () => { }; }); +jest.mock('./logs/log', () => { + const { createLogMock } = jest.requireActual('./logs/testUtils'); + const original = jest.requireActual('./logs/log'); + + return { + ...original, + log: createLogMock(), + }; +}); + async function createRegistries( preloadResults: Array<{ pluginId: string; @@ -77,8 +89,8 @@ describe('getPluginExtensions()', () => { }, }; - global.console.warn = jest.fn(); jest.mocked(reportInteraction).mockReset(); + resetLogMock(log); }); test('should return the extensions for the given placement', async () => { @@ -279,7 +291,7 @@ describe('getPluginExtensions()', () => { expect(context.title).toBe('New title from the context!'); }); - test('should catch errors in the configure() function and log them as warnings', async () => { + test('should catch errors in the configure() function and log them as error', async () => { link2.configure = jest.fn().mockImplementation(() => { throw new Error('Something went wrong!'); }); @@ -291,8 +303,11 @@ describe('getPluginExtensions()', () => { }).not.toThrow(); expect(link2.configure).toHaveBeenCalledTimes(1); - expect(global.console.warn).toHaveBeenCalledTimes(1); - expect(global.console.warn).toHaveBeenCalledWith('[Plugin Extensions] Something went wrong!'); + expect(log.error).toHaveBeenCalledTimes(1); + expect(log.error).toHaveBeenCalledWith('Failed to configure link with title "Link 2"', { + message: 'Something went wrong!', + stack: expect.stringContaining('Error: Something went wrong!'), + }); }); test('should skip the link extension if the configure() function returns with an invalid path', async () => { @@ -320,7 +335,7 @@ describe('getPluginExtensions()', () => { expect(link1.configure).toHaveBeenCalledTimes(1); expect(link2.configure).toHaveBeenCalledTimes(1); - expect(global.console.warn).toHaveBeenCalledTimes(2); + expect(log.error).toHaveBeenCalledTimes(2); }); test('should skip the extension if any of the updated props returned by the configure() function are invalid', async () => { @@ -336,7 +351,7 @@ describe('getPluginExtensions()', () => { expect(extensions).toHaveLength(0); expect(link2.configure).toHaveBeenCalledTimes(1); - expect(global.console.warn).toHaveBeenCalledTimes(1); + expect(log.error).toHaveBeenCalledTimes(1); }); test('should skip the extension if the configure() function returns a promise', async () => { @@ -347,7 +362,7 @@ describe('getPluginExtensions()', () => { expect(extensions).toHaveLength(0); expect(link2.configure).toHaveBeenCalledTimes(1); - expect(global.console.warn).toHaveBeenCalledTimes(1); + expect(log.error).toHaveBeenCalledTimes(1); }); test('should skip (hide) the extension if the configure() function returns undefined', async () => { @@ -357,7 +372,7 @@ describe('getPluginExtensions()', () => { const { extensions } = getPluginExtensions({ ...registries, extensionPointId: extensionPoint2 }); expect(extensions).toHaveLength(0); - expect(global.console.warn).toHaveBeenCalledTimes(0); // As this is intentional, no warning should be logged + expect(log.warning).toHaveBeenCalledTimes(0); // As this is intentional, no warning should be logged }); test('should pass event, context and helper to extension onClick()', async () => { @@ -386,7 +401,7 @@ describe('getPluginExtensions()', () => { ); }); - test('should catch errors in async/promise-based onClick function and log them as warnings', async () => { + test('should catch errors in async/promise-based onClick function and log them as errors', async () => { link2.path = undefined; link2.onClick = jest.fn().mockRejectedValue(new Error('testing')); @@ -400,10 +415,10 @@ describe('getPluginExtensions()', () => { expect(extensions).toHaveLength(1); expect(link2.onClick).toHaveBeenCalledTimes(1); - expect(global.console.warn).toHaveBeenCalledTimes(1); + expect(log.error).toHaveBeenCalledTimes(1); }); - test('should catch errors in the onClick() function and log them as warnings', async () => { + test('should catch errors in the onClick() function and log them as errors', async () => { link2.path = undefined; link2.onClick = jest.fn().mockImplementation(() => { throw new Error('Something went wrong!'); @@ -417,8 +432,11 @@ describe('getPluginExtensions()', () => { extension.onClick?.({} as React.MouseEvent); expect(link2.onClick).toHaveBeenCalledTimes(1); - expect(global.console.warn).toHaveBeenCalledTimes(1); - expect(global.console.warn).toHaveBeenCalledWith('[Plugin Extensions] Something went wrong!'); + expect(log.error).toHaveBeenCalledTimes(1); + expect(log.error).toHaveBeenCalledWith('Something went wrong!', { + message: 'Something went wrong!', + stack: expect.stringContaining('Error: Something went wrong!'), + }); }); test('should pass a read only context to the onClick() function', async () => { diff --git a/public/app/features/plugins/extensions/getPluginExtensions.ts b/public/app/features/plugins/extensions/getPluginExtensions.ts index bd7d55d6e0d..acaaf8d97ac 100644 --- a/public/app/features/plugins/extensions/getPluginExtensions.ts +++ b/public/app/features/plugins/extensions/getPluginExtensions.ts @@ -8,13 +8,13 @@ import { } from '@grafana/data'; import { GetPluginExtensions } from '@grafana/runtime'; +import { log } from './logs/log'; import { AddedComponentRegistryItem } from './registry/AddedComponentsRegistry'; import { AddedLinkRegistryItem } from './registry/AddedLinksRegistry'; import { RegistryType } from './registry/Registry'; import type { PluginExtensionRegistries } from './registry/types'; import { getReadOnlyProxy, - logWarning, generateExtensionId, wrapWithPluginContext, getLinkExtensionOnClick, @@ -78,8 +78,16 @@ export const getPluginExtensions: GetExtensions = ({ extensionsByPlugin[pluginId] = 0; } + const linkLog = log.child({ + pluginId, + extensionPointId, + path: addedLink.path ?? '', + title: addedLink.title, + description: addedLink.description, + onClick: typeof addedLink.onClick, + }); // Run the configure() function with the current context, and apply the ovverides - const overrides = getLinkExtensionOverrides(pluginId, addedLink, frozenContext); + const overrides = getLinkExtensionOverrides(pluginId, addedLink, linkLog, frozenContext); // configure() returned an `undefined` -> hide the extension if (addedLink.configure && overrides === undefined) { @@ -91,7 +99,7 @@ export const getPluginExtensions: GetExtensions = ({ id: generateExtensionId(pluginId, extensionPointId, addedLink.title), type: PluginExtensionTypes.link, pluginId: pluginId, - onClick: getLinkExtensionOnClick(pluginId, extensionPointId, addedLink, frozenContext), + onClick: getLinkExtensionOnClick(pluginId, extensionPointId, addedLink, linkLog, frozenContext), // Configurable properties icon: overrides?.icon || addedLink.icon, @@ -105,7 +113,10 @@ export const getPluginExtensions: GetExtensions = ({ extensionsByPlugin[pluginId] += 1; } catch (error) { if (error instanceof Error) { - logWarning(error.message); + log.error(error.message, { + stack: error.stack ?? '', + message: error.message, + }); } } } @@ -120,13 +131,20 @@ export const getPluginExtensions: GetExtensions = ({ if (extensionsByPlugin[addedComponent.pluginId] === undefined) { extensionsByPlugin[addedComponent.pluginId] = 0; } + + const componentLog = log.child({ + title: addedComponent.title, + description: addedComponent.description, + pluginId: addedComponent.pluginId, + }); + const extension: PluginExtensionComponent = { id: generateExtensionId(addedComponent.pluginId, extensionPointId, addedComponent.title), type: PluginExtensionTypes.component, pluginId: addedComponent.pluginId, title: addedComponent.title, description: addedComponent.description, - component: wrapWithPluginContext(addedComponent.pluginId, addedComponent.component), + component: wrapWithPluginContext(addedComponent.pluginId, addedComponent.component, componentLog), }; extensions.push(extension); diff --git a/public/app/features/plugins/extensions/logs/LogViewFilters.tsx b/public/app/features/plugins/extensions/logs/LogViewFilters.tsx new file mode 100644 index 00000000000..549af8fcbee --- /dev/null +++ b/public/app/features/plugins/extensions/logs/LogViewFilters.tsx @@ -0,0 +1,199 @@ +import { isEmpty } from 'lodash'; +import { ReactElement, useMemo } from 'react'; + +import { DataFrame, MatcherConfig, SelectableValue } from '@grafana/data'; +import { SceneDataProvider } from '@grafana/scenes'; +import { InlineField, InlineFieldRow, MultiSelect } from '@grafana/ui'; + +export type LogFilter = { + pluginIds?: Set; + extensionPointIds?: Set; + severity?: Set; + initial?: string; +}; + +type LogViewFiltersProps = { + provider: SceneDataProvider; + filteredProvider: SceneDataProvider; + filter: LogFilter; + onChange: (filter: LogFilter) => void; +}; + +export function LogViewFilters({ provider, filteredProvider, filter, onChange }: LogViewFiltersProps): ReactElement { + const { pluginIds, extensionPointIds, severity } = useLogFilters(provider, filteredProvider, filter); + + const onChangePluginIds = (values: Array>) => { + const update = { + ...filter, + pluginIds: mapToSet(values), + }; + + if (isEmpty(filter.extensionPointIds) && isEmpty(filter.severity)) { + update.initial = isEmpty(values) ? undefined : 'pluginId'; + } + + onChange(update); + }; + + const onChangeExtensionPointIds = (values: Array>) => { + const update = { + ...filter, + extensionPointIds: mapToSet(values), + }; + + if (isEmpty(filter.pluginIds) && isEmpty(filter.severity)) { + update.initial = isEmpty(values) ? undefined : 'extensionPointId'; + } + + onChange(update); + }; + + const onChangeSeverity = (values: Array>) => { + const update = { + ...filter, + severity: mapToSet(values), + }; + + if (isEmpty(filter.pluginIds) && isEmpty(filter.extensionPointIds)) { + update.initial = isEmpty(values) ? undefined : 'severity'; + } + + onChange(update); + }; + + return ( + + + + + + + + + + + + ); +} + +export type FilterConfig = { + fieldName: string; + config: MatcherConfig; +}; + +type LogFilterOptions = { + pluginIds: Array>; + extensionPointIds: Array>; + severity: Array>; +}; + +function useLogFilters( + provider: SceneDataProvider, + filteredProvider: SceneDataProvider, + filter: LogFilter +): LogFilterOptions { + const { data } = provider.useState(); + const { data: filteredData } = filteredProvider.useState(); + + return useMemo(() => { + if (data && data?.series.length > 1) { + console.warn('LogViewFilter does not support multiple series in query result.'); + } + + const frame = data?.series[0]; + const filteredFrame = filteredData?.series[0]; + + if (!frame) { + return { + pluginIds: [], + extensionPointIds: [], + severity: [], + }; + } + + if (!filteredFrame) { + return toFilterOptions({ + severity: frame, + pluginId: frame, + extensionPointId: frame, + }); + } + + switch (filter.initial) { + case 'extensionPointId': + return toFilterOptions({ + severity: filteredFrame, + pluginId: filteredFrame, + extensionPointId: frame, + }); + + case 'severity': + return toFilterOptions({ + severity: frame, + pluginId: filteredFrame, + extensionPointId: filteredFrame, + }); + + case 'pluginId': + return toFilterOptions({ + severity: filteredFrame, + pluginId: frame, + extensionPointId: filteredFrame, + }); + + default: + return toFilterOptions({ + severity: frame, + pluginId: frame, + extensionPointId: frame, + }); + } + }, [data, filteredData, filter]); +} + +function mapToSet(selected: Array>): Set | undefined { + if (selected.length <= 0) { + return undefined; + } + + return selected.reduce((set, selectable) => { + if (selectable.value) { + set.add(selectable.value); + } + return set; + }, new Set()); +} + +function toSelectableArray(source: Set): Array> { + return Array.from(source).reduce((all: Array>, current) => { + if (!current) { + return all; + } + all.push({ + value: current, + label: current, + }); + return all; + }, []); +} + +function toFilterOptions(sources: { + severity: DataFrame; + pluginId: DataFrame; + extensionPointId: DataFrame; +}): LogFilterOptions { + const { severity, pluginId, extensionPointId } = sources; + const severityIndex = severity.fields.findIndex((f) => f.name === 'severity'); + const pluginIdIndex = pluginId.fields.findIndex((f) => f.name === 'pluginId'); + const extensionPointIdIndex = extensionPointId.fields.findIndex((f) => f.name === 'extensionPointId'); + + const severities = new Set(severity.fields[severityIndex].values); + const pluginIds = new Set(pluginId.fields[pluginIdIndex].values); + const extensionPointIds = new Set(extensionPointId.fields[extensionPointIdIndex].values); + + return { + severity: toSelectableArray(severities), + pluginIds: toSelectableArray(pluginIds), + extensionPointIds: toSelectableArray(extensionPointIds), + }; +} diff --git a/public/app/features/plugins/extensions/logs/LogViewer.tsx b/public/app/features/plugins/extensions/logs/LogViewer.tsx new file mode 100644 index 00000000000..155a97611d5 --- /dev/null +++ b/public/app/features/plugins/extensions/logs/LogViewer.tsx @@ -0,0 +1,62 @@ +import { nanoid } from 'nanoid'; +import { ReactElement, useState } from 'react'; + +import { sceneUtils, VizConfigBuilders } from '@grafana/scenes'; +import { + SceneContextProvider, + useDataTransformer, + useQueryRunner, + VizGridLayout, + VizPanel, +} from '@grafana/scenes-react'; +import { Page } from 'app/core/components/Page/Page'; + +import { LogFilter, LogViewFilters } from './LogViewFilters'; +import { ExtensionsLogDataSource } from './dataSource'; +import { createFilterTransformation } from './filterTransformation'; +import { log } from './log'; + +const DATASOURCE_REF = { + uid: nanoid(), + type: 'grafana-extensionslog-datasource', +}; + +const logsViz = VizConfigBuilders.logs().build(); + +sceneUtils.registerRuntimeDataSource({ + dataSource: new ExtensionsLogDataSource(DATASOURCE_REF.type, DATASOURCE_REF.uid, log), +}); + +export default function LogViewer(): ReactElement { + return ( + + + + ); +} + +function LogViewScene(): ReactElement | null { + const [filter, setFilter] = useState({}); + + const data = useQueryRunner({ + datasource: DATASOURCE_REF, + queries: [{ refId: 'A' }], + liveStreaming: true, + }); + + const filteredData = useDataTransformer({ + transformations: [createFilterTransformation(filter)], + data: data, + }); + + return ( + } + > + + + + + ); +} diff --git a/public/app/features/plugins/extensions/logs/dataSource.test.ts b/public/app/features/plugins/extensions/logs/dataSource.test.ts new file mode 100644 index 00000000000..e6b2db93a2c --- /dev/null +++ b/public/app/features/plugins/extensions/logs/dataSource.test.ts @@ -0,0 +1,127 @@ +import { nanoid } from 'nanoid'; +import { lastValueFrom, of } from 'rxjs'; + +import { DataQueryRequest, dateTime, LoadingState } from '@grafana/data'; + +import { ExtensionsLogDataSource } from './dataSource'; +import { log } from './log'; + +jest.mock('./log', () => { + const original = jest.requireActual('./log'); + return { + ...original, + log: { + asObservable: () => + of( + { + level: 'info', + labels: { + test: 'test', + }, + timestamp: Date.now(), + id: nanoid(), + message: 'a message', + pluginId: 'grafana-k8-app', + extensionPointId: 'grafana/dashboards/panel/menu', + }, + { + level: 'debug', + labels: { + title: 'a link', + onClick: 'function', + }, + timestamp: Date.now(), + id: nanoid(), + message: 'another message', + } + ), + }, + }; +}); + +describe('ExtensionsLogDataSource', () => { + const dataSource = new ExtensionsLogDataSource('pluginId', 'ds-uid', log); + + it('should return a stream when querying for data', async () => { + const response = await lastValueFrom(dataSource.query(createRequest())); + expect(response.state).toBe(LoadingState.Streaming); + }); + + it('should return logs as data frames when querying for data', async () => { + const { data } = await lastValueFrom(dataSource.query(createRequest())); + expect(data).toStrictEqual([ + { + refId: 'A', + meta: { + type: 'log-lines', + }, + length: 2, + fields: [ + { + config: expect.any(Object), + name: 'timestamp', + type: 'time', + values: [expect.any(Number), expect.any(Number)], + }, + { + config: expect.any(Object), + name: 'body', + type: 'string', + values: ['another message', 'a message'], + }, + { + config: expect.any(Object), + name: 'severity', + type: 'string', + values: ['debug', 'info'], + }, + { + config: expect.any(Object), + name: 'id', + type: 'string', + values: [expect.any(String), expect.any(String)], + }, + { + config: expect.any(Object), + name: 'labels', + type: 'other', + values: [{ onClick: 'function', title: 'a link' }, { test: 'test' }], + }, + { + config: expect.any(Object), + name: 'pluginId', + type: 'string', + values: [null, 'grafana-k8-app'], + }, + { + config: expect.any(Object), + name: 'extensionPointId', + type: 'string', + values: [null, 'grafana/dashboards/panel/menu'], + }, + ], + }, + ]); + }); +}); + +function createRequest(): DataQueryRequest { + return { + requestId: '', + interval: '', + intervalMs: 0, + range: { + from: dateTime(), + to: dateTime(), + raw: { + from: '', + to: '', + }, + }, + scopedVars: {}, + targets: [{ refId: 'A' }], + timezone: '', + app: '', + startTime: Date.now(), + }; +} diff --git a/public/app/features/plugins/extensions/logs/dataSource.ts b/public/app/features/plugins/extensions/logs/dataSource.ts new file mode 100644 index 00000000000..8b5ba2a4df1 --- /dev/null +++ b/public/app/features/plugins/extensions/logs/dataSource.ts @@ -0,0 +1,104 @@ +import { Observable, scan } from 'rxjs'; + +import { + createDataFrame, + DataFrame, + DataFrameType, + DataQueryRequest, + DataQueryResponse, + FieldType, + LoadingState, + TestDataSourceResponse, +} from '@grafana/data'; +import { RuntimeDataSource, SceneDataQuery } from '@grafana/scenes'; + +import { ExtensionsLog, ExtensionsLogItem } from './log'; + +export class ExtensionsLogDataSource extends RuntimeDataSource { + constructor( + public readonly pluginId: string, + public readonly uid: string, + private readonly extensionsLog: ExtensionsLog + ) { + super(pluginId, uid); + } + + query(request: DataQueryRequest): Observable { + const [query] = request.targets; + + return this.extensionsLog.asObservable().pipe( + scan( + (response, item) => { + const [existing] = response.data; + + return { + data: [createFrame(query, item, existing)], + key: query.key ?? query.refId, + state: LoadingState.Streaming, + }; + }, + { + data: [], + key: query.key ?? query.refId, + state: LoadingState.Streaming, + } + ) + ); + } + + testDatasource(): Promise { + return Promise.resolve({ status: 'success', message: 'OK' }); + } +} + +function createFrame(query: SceneDataQuery, item: ExtensionsLogItem, existing?: DataFrame): DataFrame { + const timestamps = existing?.fields?.[0]?.values ?? []; + const messages = existing?.fields?.[1]?.values ?? []; + const levels = existing?.fields?.[2]?.values ?? []; + const ids = existing?.fields?.[3]?.values ?? []; + const labels = existing?.fields?.[4]?.values ?? []; + const pluginIds = existing?.fields?.[5]?.values ?? []; + const extensionPointIds = existing?.fields?.[6]?.values ?? []; + + return createDataFrame({ + refId: query.refId, + meta: { type: DataFrameType.LogLines }, + fields: [ + { + name: 'timestamp', + type: FieldType.time, + values: [item.timestamp, ...timestamps], + }, + { + name: 'body', + type: FieldType.string, + values: [item.message, ...messages], + }, + { + name: 'severity', + type: FieldType.string, + values: [item.level, ...levels], + }, + { + name: 'id', + type: FieldType.string, + values: [item.id, ...ids], + }, + { + name: 'labels', + type: FieldType.other, + values: [item.labels, ...labels], + }, + { + name: 'pluginId', + type: FieldType.string, + values: [item.pluginId ?? null, ...pluginIds], + }, + { + name: 'extensionPointId', + type: FieldType.string, + values: [item.extensionPointId ?? null, ...extensionPointIds], + }, + ], + }); +} diff --git a/public/app/features/plugins/extensions/logs/filterTransformation.test.ts b/public/app/features/plugins/extensions/logs/filterTransformation.test.ts new file mode 100644 index 00000000000..17ed1800535 --- /dev/null +++ b/public/app/features/plugins/extensions/logs/filterTransformation.test.ts @@ -0,0 +1,361 @@ +import { lastValueFrom, Observable } from 'rxjs'; + +import { DataFrame, FieldType, toDataFrame } from '@grafana/data'; + +import { LogFilter } from './LogViewFilters'; +import { createFilterTransformation } from './filterTransformation'; + +const data = [ + toDataFrame({ + name: 'A', + length: 3, + fields: [ + { name: 'pluginId', type: FieldType.string, values: ['grafana-k8s-app', 'grafana', 'mckn-funnel-panel'] }, + { + name: 'extensionPointId', + type: FieldType.string, + values: [ + 'grafana/explore/toolbar/actions', + 'grafana-k8s-app/clusters/view/v1', + 'grafana/dashboards/panel/menu/v1', + ], + }, + { name: 'severity', type: FieldType.string, values: ['info', 'info', 'info'] }, + ], + }), + toDataFrame({ + name: 'B', + length: 3, + fields: [ + { name: 'pluginId', type: FieldType.string, values: ['grafana-k8s-app', 'grafana', 'mckn-funnel-panel'] }, + { + name: 'extensionPointId', + type: FieldType.string, + values: [ + 'grafana-k8s-app/clusters/view/v1', + 'grafana/dashboards/panel/menu/v1', + 'grafana/explore/toolbar/actions', + ], + }, + { name: 'severity', type: FieldType.string, values: ['debug', 'warning', 'error'] }, + ], + }), +]; + +describe('Transform data frames by filtering', () => { + it('should keep all rows when no filter is applied', async () => { + const [a, b] = await runTransformationWithFilter({}, data); + expect(a.length).toBe(data[0].length); + expect(b.length).toBe(data[1].length); + }); + + it('should exclude all rows not matching pluginId', async () => { + const filter = { + pluginIds: new Set(['grafana-k8s-app']), + }; + const [a, b] = await runTransformationWithFilter(filter, data); + + expect(a.fields).toStrictEqual([ + { + config: {}, + name: 'pluginId', + type: FieldType.string, + values: ['grafana-k8s-app'], + }, + { + config: {}, + name: 'extensionPointId', + type: FieldType.string, + values: ['grafana/explore/toolbar/actions'], + }, + { + config: {}, + name: 'severity', + type: FieldType.string, + values: ['info'], + }, + ]); + + expect(b.fields).toStrictEqual([ + { + config: {}, + name: 'pluginId', + type: FieldType.string, + values: ['grafana-k8s-app'], + }, + { + config: {}, + name: 'extensionPointId', + type: FieldType.string, + values: ['grafana-k8s-app/clusters/view/v1'], + }, + { + config: {}, + name: 'severity', + type: FieldType.string, + values: ['debug'], + }, + ]); + }); + + it('should exclude all rows not matching severity', async () => { + const filter = { + severity: new Set(['debug']), + }; + const [a, b] = await runTransformationWithFilter(filter, data); + + expect(a.fields).toStrictEqual([ + { + config: {}, + name: 'pluginId', + type: FieldType.string, + values: [], + }, + { + config: {}, + name: 'extensionPointId', + type: FieldType.string, + values: [], + }, + { + config: {}, + name: 'severity', + type: FieldType.string, + values: [], + }, + ]); + + expect(b.fields).toStrictEqual([ + { + config: {}, + name: 'pluginId', + type: FieldType.string, + values: ['grafana-k8s-app'], + }, + { + config: {}, + name: 'extensionPointId', + type: FieldType.string, + values: ['grafana-k8s-app/clusters/view/v1'], + }, + { + config: {}, + name: 'severity', + type: FieldType.string, + values: ['debug'], + }, + ]); + }); + + it('should exclude all rows not matching extensionPointId', async () => { + const filter = { + extensionPointIds: new Set(['grafana/dashboards/panel/menu/v1']), + }; + const [a, b] = await runTransformationWithFilter(filter, data); + + expect(a.fields).toStrictEqual([ + { + config: {}, + name: 'pluginId', + type: FieldType.string, + values: ['mckn-funnel-panel'], + }, + { + config: {}, + name: 'extensionPointId', + type: FieldType.string, + values: ['grafana/dashboards/panel/menu/v1'], + }, + { + config: {}, + name: 'severity', + type: FieldType.string, + values: ['info'], + }, + ]); + + expect(b.fields).toStrictEqual([ + { + config: {}, + name: 'pluginId', + type: FieldType.string, + values: ['grafana'], + }, + { + config: {}, + name: 'extensionPointId', + type: FieldType.string, + values: ['grafana/dashboards/panel/menu/v1'], + }, + { + config: {}, + name: 'severity', + type: FieldType.string, + values: ['warning'], + }, + ]); + }); + + it('should exclude all rows not matching pluginId and severity', async () => { + const filter = { + pluginIds: new Set(['grafana-k8s-app']), + severity: new Set(['debug']), + }; + const [a, b] = await runTransformationWithFilter(filter, data); + + expect(a.fields).toStrictEqual([ + { + config: {}, + name: 'pluginId', + type: FieldType.string, + values: [], + }, + { + config: {}, + name: 'extensionPointId', + type: FieldType.string, + values: [], + }, + { + config: {}, + name: 'severity', + type: FieldType.string, + values: [], + }, + ]); + + expect(b.fields).toStrictEqual([ + { + config: {}, + name: 'pluginId', + type: FieldType.string, + values: ['grafana-k8s-app'], + }, + { + config: {}, + name: 'extensionPointId', + type: FieldType.string, + values: ['grafana-k8s-app/clusters/view/v1'], + }, + { + config: {}, + name: 'severity', + type: FieldType.string, + values: ['debug'], + }, + ]); + }); + + it('should exclude all rows not matching pluginId and severity', async () => { + const filter = { + pluginIds: new Set(['grafana-k8s-app', 'grafana']), + severity: new Set(['info']), + }; + const [a, b] = await runTransformationWithFilter(filter, data); + + expect(a.fields).toStrictEqual([ + { + config: {}, + name: 'pluginId', + type: FieldType.string, + values: ['grafana-k8s-app', 'grafana'], + }, + { + config: {}, + name: 'extensionPointId', + type: FieldType.string, + values: ['grafana/explore/toolbar/actions', 'grafana-k8s-app/clusters/view/v1'], + }, + { + config: {}, + name: 'severity', + type: FieldType.string, + values: ['info', 'info'], + }, + ]); + + expect(b.fields).toStrictEqual([ + { + config: {}, + name: 'pluginId', + type: FieldType.string, + values: [], + }, + { + config: {}, + name: 'extensionPointId', + type: FieldType.string, + values: [], + }, + { + config: {}, + name: 'severity', + type: FieldType.string, + values: [], + }, + ]); + }); + + it('should exclude all rows not matching one of pluginId with severity and extensionPointId', async () => { + const filter = { + pluginIds: new Set(['grafana-k8s-app', 'grafana']), + severity: new Set(['info']), + extensionPointIds: new Set(['grafana/explore/toolbar/actions']), + }; + const [a, b] = await runTransformationWithFilter(filter, data); + + expect(a.fields).toStrictEqual([ + { + config: {}, + name: 'pluginId', + type: FieldType.string, + values: ['grafana-k8s-app'], + }, + { + config: {}, + name: 'extensionPointId', + type: FieldType.string, + values: ['grafana/explore/toolbar/actions'], + }, + { + config: {}, + name: 'severity', + type: FieldType.string, + values: ['info'], + }, + ]); + + expect(b.fields).toStrictEqual([ + { + config: {}, + name: 'pluginId', + type: FieldType.string, + values: [], + }, + { + config: {}, + name: 'extensionPointId', + type: FieldType.string, + values: [], + }, + { + config: {}, + name: 'severity', + type: FieldType.string, + values: [], + }, + ]); + }); +}); + +function runTransformationWithFilter(filter: LogFilter, frames: DataFrame[]): Promise { + const transformation = createFilterTransformation(filter); + const operator = transformation({ interpolate: () => '' }); + + return lastValueFrom( + new Observable((sub) => { + sub.next(frames); + sub.complete(); + }).pipe(operator) + ); +} diff --git a/public/app/features/plugins/extensions/logs/filterTransformation.ts b/public/app/features/plugins/extensions/logs/filterTransformation.ts new file mode 100644 index 00000000000..32852c38bdc --- /dev/null +++ b/public/app/features/plugins/extensions/logs/filterTransformation.ts @@ -0,0 +1,86 @@ +import { isEmpty } from 'lodash'; +import { Observable, scan } from 'rxjs'; + +import { createDataFrame, CustomTransformOperator, DataFrame, PartialDataFrame } from '@grafana/data'; + +import { LogFilter } from './LogViewFilters'; + +export function createFilterTransformation(filter: LogFilter): CustomTransformOperator { + return function cascadingFilterTransformation() { + return function (source: Observable) { + return source.pipe( + scan((filtered: DataFrame[], current) => { + if (isEmpty(filter.extensionPointIds) && isEmpty(filter.pluginIds) && isEmpty(filter.severity)) { + return current; + } + + for (const frame of current) { + const pluginIdIndex = frame.fields.findIndex((f) => f.name === 'pluginId'); + const extensionPointIdIndex = frame.fields.findIndex((f) => f.name === 'extensionPointId'); + const severityIndex = frame.fields.findIndex((f) => f.name === 'severity'); + + if (pluginIdIndex === -1 && !isEmpty(filter.pluginIds)) { + continue; + } + + if (extensionPointIdIndex === -1 && !isEmpty(filter.extensionPointIds)) { + continue; + } + + if (severityIndex === -1 && !isEmpty(filter.severity)) { + continue; + } + + const target: PartialDataFrame = { + ...frame, + fields: frame.fields.map((f) => ({ + ...f, + values: [], + })), + }; + + for (let index = 0; index < frame.length; index++) { + const pluginId = frame.fields[pluginIdIndex].values[index]; + const extensionPointId = frame.fields[extensionPointIdIndex].values[index]; + const severity = frame.fields[severityIndex].values[index]; + + if (!isEmpty(filter.pluginIds) && !filter.pluginIds?.has(pluginId)) { + continue; + } + + if (!isEmpty(filter.extensionPointIds) && !filter.extensionPointIds?.has(extensionPointId)) { + continue; + } + + if (!isEmpty(filter.severity) && !filter.severity?.has(severity)) { + continue; + } + + copyRow(frame, target, index); + } + + filtered.push(createDataFrame(target)); + } + + return filtered; + }, []) + ); + }; + }; +} + +function copyRow(source: DataFrame, target: PartialDataFrame, rowIndex: number) { + for (let index = 0; index < source.fields.length; index++) { + const field = source.fields[index]; + + if (!target.fields[index]) { + target.fields[index] = { + ...field, + values: [], + }; + } + + const value = source.fields[index].values[rowIndex]; + target.fields[index].values?.push(value); + } +} diff --git a/public/app/features/plugins/extensions/logs/log.ts b/public/app/features/plugins/extensions/logs/log.ts new file mode 100644 index 00000000000..a78d42ccea0 --- /dev/null +++ b/public/app/features/plugins/extensions/logs/log.ts @@ -0,0 +1,94 @@ +import { isString } from 'lodash'; +import { nanoid } from 'nanoid'; +import { Observable, ReplaySubject } from 'rxjs'; + +import { Labels, LogLevel } from '@grafana/data'; + +export type ExtensionsLogItem = { + level: LogLevel; + timestamp: number; + labels: Labels; + message: string; + id: string; + pluginId?: string; + extensionPointId?: string; +}; + +const channelName = 'ui-extension-logs'; + +export class ExtensionsLog { + private baseLabels: Labels | undefined; + private subject: ReplaySubject | undefined; + private channel: BroadcastChannel; + + constructor(baseLabels?: Labels, subject?: ReplaySubject, channel?: BroadcastChannel) { + this.baseLabels = baseLabels; + this.channel = channel ?? new BroadcastChannel(channelName); + this.subject = subject; + } + + info(message: string, labels?: Labels): void { + this.log(LogLevel.info, message, labels); + } + + warning(message: string, labels?: Labels): void { + this.log(LogLevel.warning, message, labels); + } + + error(message: string, labels?: Labels): void { + this.log(LogLevel.error, message, labels); + } + + debug(message: string, labels?: Labels): void { + this.log(LogLevel.debug, message, labels); + } + + trace(message: string, labels?: Labels): void { + this.log(LogLevel.trace, message, labels); + } + + fatal(message: string, labels?: Labels): void { + this.log(LogLevel.fatal, message, labels); + } + + private log(level: LogLevel, message: string, labels?: Labels): void { + const combinedLabels = { ...labels, ...this.baseLabels }; + const { pluginId, extensionPointId } = combinedLabels; + + const item: ExtensionsLogItem = { + level: level, + labels: combinedLabels, + timestamp: Date.now(), + id: nanoid(), + message: message, + pluginId: isString(pluginId) ? pluginId : undefined, + extensionPointId: isString(extensionPointId) ? extensionPointId : undefined, + }; + + this.channel.postMessage(item); + } + + asObservable(): Observable { + if (!this.subject) { + // Lazily create the subject on first subscription to prevent + // to create buffers when no subscribers exists + this.subject = new ReplaySubject(1000, 1000 * 60 * 10); + this.channel.onmessage = (msg: MessageEvent) => this.subject?.next(msg.data); + } + + return this.subject.asObservable(); + } + + child(labels: Labels): ExtensionsLog { + return new ExtensionsLog( + { + ...labels, + ...this.baseLabels, + }, + this.subject, + this.channel + ); + } +} + +export const log = new ExtensionsLog(); diff --git a/public/app/features/plugins/extensions/logs/testUtils.ts b/public/app/features/plugins/extensions/logs/testUtils.ts new file mode 100644 index 00000000000..2877b9075c7 --- /dev/null +++ b/public/app/features/plugins/extensions/logs/testUtils.ts @@ -0,0 +1,31 @@ +import { ExtensionsLog } from './log'; + +export function createLogMock(): ExtensionsLog { + const { log: original } = jest.requireActual('./log'); + + const logMock = { + error: jest.fn(), + warning: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), + fatal: jest.fn(), + child: jest.fn(), + }; + + logMock.child.mockReturnValue(logMock); + + return { + ...original, + ...logMock, + }; +} + +export function resetLogMock(log: ExtensionsLog): void { + jest.mocked(log.error).mockReset(); + jest.mocked(log.warning).mockReset(); + jest.mocked(log.info).mockReset(); + jest.mocked(log.debug).mockReset(); + jest.mocked(log.trace).mockReset(); + jest.mocked(log.fatal).mockReset(); +} diff --git a/public/app/features/plugins/extensions/registry/AddedComponentsRegistry.test.ts b/public/app/features/plugins/extensions/registry/AddedComponentsRegistry.test.ts index 8e0041d185c..fcf3d4616bb 100644 --- a/public/app/features/plugins/extensions/registry/AddedComponentsRegistry.test.ts +++ b/public/app/features/plugins/extensions/registry/AddedComponentsRegistry.test.ts @@ -4,6 +4,8 @@ import { firstValueFrom } from 'rxjs'; import { PluginLoadingStrategy } from '@grafana/data'; import { config } from '@grafana/runtime'; +import { log } from '../logs/log'; +import { resetLogMock } from '../logs/testUtils'; import { isGrafanaDevMode } from '../utils'; import { AddedComponentsRegistry } from './AddedComponentsRegistry'; @@ -17,8 +19,17 @@ jest.mock('../utils', () => ({ isGrafanaDevMode: jest.fn().mockReturnValue(false), })); +jest.mock('../logs/log', () => { + const { createLogMock } = jest.requireActual('../logs/testUtils'); + const original = jest.requireActual('../logs/log'); + + return { + ...original, + log: createLogMock(), + }; +}); + describe('AddedComponentsRegistry', () => { - const consoleWarn = jest.fn(); const originalApps = config.apps; const pluginId = 'grafana-basic-app'; const appPluginConfig = { @@ -47,8 +58,7 @@ describe('AddedComponentsRegistry', () => { }; beforeEach(() => { - global.console.warn = consoleWarn; - consoleWarn.mockReset(); + resetLogMock(log); jest.mocked(isGrafanaDevMode).mockReturnValue(false); config.apps = { [pluginId]: appPluginConfig, @@ -363,8 +373,8 @@ describe('AddedComponentsRegistry', () => { ], }); - expect(consoleWarn).toHaveBeenCalledWith( - `[Plugin Extensions] Added component "Component 1 title": it's recommended to suffix the extension point id ("${extensionPointId}") with a version, e.g 'myorg-basic-app/extension-point/v1'.` + expect(log.warning).toHaveBeenCalledWith( + `Added component "Component 1 title": it's recommended to suffix the extension point id ("${extensionPointId}") with a version, e.g 'myorg-basic-app/extension-point/v1'.` ); const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(1); @@ -386,8 +396,8 @@ describe('AddedComponentsRegistry', () => { ], }); - expect(consoleWarn).toHaveBeenCalledWith( - "[Plugin Extensions] Could not register added component with title 'Component 1 title'. Reason: Description is missing." + expect(log.error).toHaveBeenCalledWith( + "Could not register added component with title 'Component 1 title'. Reason: Description is missing." ); const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(0); @@ -401,7 +411,7 @@ describe('AddedComponentsRegistry', () => { pluginId, configs: [ { - title: 'Component 1 title', + title: '', description: '', targets: [extensionPointId], component: () => React.createElement('div', null, 'Hello World1'), @@ -409,9 +419,7 @@ describe('AddedComponentsRegistry', () => { ], }); - expect(consoleWarn).toHaveBeenCalledWith( - "[Plugin Extensions] Could not register added component with title 'Component 1 title'. Reason: Description is missing." - ); + expect(log.error).toHaveBeenCalledWith('Could not register added component. Reason: Title is missing.'); const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(0); @@ -497,7 +505,7 @@ describe('AddedComponentsRegistry', () => { const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(0); - expect(consoleWarn).toHaveBeenCalled(); + expect(log.warning).toHaveBeenCalled(); }); it('should register a component added by a core Grafana in dev-mode even if the meta-info is missing', async () => { @@ -520,7 +528,7 @@ describe('AddedComponentsRegistry', () => { const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(1); - expect(consoleWarn).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should register a component added by a plugin in production mode even if the meta-info is missing', async () => { @@ -546,7 +554,7 @@ describe('AddedComponentsRegistry', () => { const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(1); - expect(consoleWarn).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should register a component added by a plugin in dev-mode if the meta-info is present', async () => { @@ -572,6 +580,6 @@ describe('AddedComponentsRegistry', () => { const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(1); - expect(consoleWarn).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); }); diff --git a/public/app/features/plugins/extensions/registry/AddedComponentsRegistry.ts b/public/app/features/plugins/extensions/registry/AddedComponentsRegistry.ts index 1760f3d36cf..a57f56c6008 100644 --- a/public/app/features/plugins/extensions/registry/AddedComponentsRegistry.ts +++ b/public/app/features/plugins/extensions/registry/AddedComponentsRegistry.ts @@ -2,7 +2,7 @@ import { ReplaySubject } from 'rxjs'; import { PluginExtensionAddedComponentConfig } from '@grafana/data'; -import { isAddedComponentMetaInfoMissing, isGrafanaDevMode, logWarning, wrapWithPluginContext } from '../utils'; +import { isAddedComponentMetaInfoMissing, isGrafanaDevMode, wrapWithPluginContext } from '../utils'; import { extensionPointEndsWithVersion, isGrafanaCoreExtensionPoint, isReactComponent } from '../validators'; import { PluginExtensionConfigs, Registry, RegistryType } from './Registry'; @@ -34,42 +34,58 @@ export class AddedComponentsRegistry extends Registry< const { pluginId, configs } = item; for (const config of configs) { + const configLog = this.logger.child({ + description: config.description, + title: config.title, + pluginId, + }); + if (!isReactComponent(config.component)) { - logWarning( - `Could not register added component with title '${config.title}'. Reason: The provided component is not a valid React component.` + configLog.error( + `Could not register added component. Reason: The provided component is not a valid React component.` ); continue; } if (!config.title) { - logWarning(`Could not register added component with title '${config.title}'. Reason: Title is missing.`); + configLog.error(`Could not register added component. Reason: Title is missing.`); continue; } if (!config.description) { - logWarning(`Could not register added component with title '${config.title}'. Reason: Description is missing.`); + configLog.error( + `Could not register added component with title '${config.title}'. Reason: Description is missing.` + ); continue; } - if (pluginId !== 'grafana' && isGrafanaDevMode() && isAddedComponentMetaInfoMissing(pluginId, config)) { + if ( + pluginId !== 'grafana' && + isGrafanaDevMode() && + isAddedComponentMetaInfoMissing(pluginId, config, configLog) + ) { continue; } const extensionPointIds = Array.isArray(config.targets) ? config.targets : [config.targets]; for (const extensionPointId of extensionPointIds) { + const pointIdLog = configLog.child({ extensionPointId }); + if (!isGrafanaCoreExtensionPoint(extensionPointId) && !extensionPointEndsWithVersion(extensionPointId)) { - logWarning( + pointIdLog.warning( `Added component "${config.title}": it's recommended to suffix the extension point id ("${extensionPointId}") with a version, e.g 'myorg-basic-app/extension-point/v1'.` ); } const result = { pluginId, - component: wrapWithPluginContext(pluginId, config.component), + component: wrapWithPluginContext(pluginId, config.component, pointIdLog), description: config.description, title: config.title, }; + pointIdLog.debug(`Added component from '${pluginId}' to '${extensionPointId}'`); + if (!(extensionPointId in registry)) { registry[extensionPointId] = [result]; } else { diff --git a/public/app/features/plugins/extensions/registry/AddedLinksRegistry.test.ts b/public/app/features/plugins/extensions/registry/AddedLinksRegistry.test.ts index 45735806af9..3dd5b7c7f7d 100644 --- a/public/app/features/plugins/extensions/registry/AddedLinksRegistry.test.ts +++ b/public/app/features/plugins/extensions/registry/AddedLinksRegistry.test.ts @@ -3,6 +3,8 @@ import { firstValueFrom } from 'rxjs'; import { PluginLoadingStrategy } from '@grafana/data'; import { config } from '@grafana/runtime'; +import { log } from '../logs/log'; +import { resetLogMock } from '../logs/testUtils'; import { isGrafanaDevMode } from '../utils'; import { AddedLinksRegistry } from './AddedLinksRegistry'; @@ -16,9 +18,18 @@ jest.mock('../utils', () => ({ isGrafanaDevMode: jest.fn().mockReturnValue(false), })); +jest.mock('../logs/log', () => { + const { createLogMock } = jest.requireActual('../logs/testUtils'); + const original = jest.requireActual('../logs/log'); + + return { + ...original, + log: createLogMock(), + }; +}); + describe('AddedLinksRegistry', () => { const originalApps = config.apps; - const consoleWarn = jest.fn(); const pluginId = 'grafana-basic-app'; const appPluginConfig = { id: pluginId, @@ -46,8 +57,7 @@ describe('AddedLinksRegistry', () => { }; beforeEach(() => { - global.console.warn = consoleWarn; - consoleWarn.mockReset(); + resetLogMock(log); jest.mocked(isGrafanaDevMode).mockReturnValue(false); config.apps = { [pluginId]: appPluginConfig, @@ -503,7 +513,7 @@ describe('AddedLinksRegistry', () => { ], }); - expect(consoleWarn).toHaveBeenCalled(); + expect(log.error).toHaveBeenCalled(); observable.subscribe(subscribeCallback); expect(subscribeCallback).toHaveBeenCalledTimes(1); @@ -531,7 +541,7 @@ describe('AddedLinksRegistry', () => { ], }); - expect(consoleWarn).toHaveBeenCalled(); + expect(log.error).toHaveBeenCalled(); observable.subscribe(subscribeCallback); expect(subscribeCallback).toHaveBeenCalledTimes(1); @@ -559,7 +569,7 @@ describe('AddedLinksRegistry', () => { ], }); - expect(consoleWarn).toHaveBeenCalled(); + expect(log.error).toHaveBeenCalled(); observable.subscribe(subscribeCallback); expect(subscribeCallback).toHaveBeenCalledTimes(1); @@ -651,7 +661,7 @@ describe('AddedLinksRegistry', () => { const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(0); - expect(consoleWarn).toHaveBeenCalled(); + expect(log.warning).toHaveBeenCalled(); }); it('should register a link added by core Grafana in dev-mode even if the meta-info is missing', async () => { @@ -675,7 +685,7 @@ describe('AddedLinksRegistry', () => { const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(1); - expect(consoleWarn).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should register a link added by a plugin in production mode even if the meta-info is missing', async () => { @@ -702,7 +712,7 @@ describe('AddedLinksRegistry', () => { const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(1); - expect(consoleWarn).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should register a link added by a plugin in dev-mode if the meta-info is present', async () => { @@ -729,6 +739,6 @@ describe('AddedLinksRegistry', () => { const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(1); - expect(consoleWarn).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); }); diff --git a/public/app/features/plugins/extensions/registry/AddedLinksRegistry.ts b/public/app/features/plugins/extensions/registry/AddedLinksRegistry.ts index d2f6207e496..0e978b993c6 100644 --- a/public/app/features/plugins/extensions/registry/AddedLinksRegistry.ts +++ b/public/app/features/plugins/extensions/registry/AddedLinksRegistry.ts @@ -3,7 +3,7 @@ import { ReplaySubject } from 'rxjs'; import { IconName, PluginExtensionAddedLinkConfig } from '@grafana/data'; import { PluginAddedLinksConfigureFunc, PluginExtensionEventHelpers } from '@grafana/data/src/types/pluginExtensions'; -import { isAddedLinkMetaInfoMissing, isGrafanaDevMode, logWarning } from '../utils'; +import { isAddedLinkMetaInfoMissing, isGrafanaDevMode } from '../utils'; import { extensionPointEndsWithVersion, isConfigureFnValid, @@ -43,43 +43,53 @@ export class AddedLinksRegistry extends Registry ({ isGrafanaDevMode: jest.fn().mockReturnValue(false), })); +jest.mock('../logs/log', () => { + const { createLogMock } = jest.requireActual('../logs/testUtils'); + const original = jest.requireActual('../logs/log'); + + return { + ...original, + log: createLogMock(), + }; +}); + describe('ExposedComponentsRegistry', () => { - const consoleWarn = jest.fn(); const originalApps = config.apps; const pluginId = 'grafana-basic-app'; const appPluginConfig = { @@ -47,8 +58,7 @@ describe('ExposedComponentsRegistry', () => { }; beforeEach(() => { - global.console.warn = consoleWarn; - consoleWarn.mockReset(); + resetLogMock(log); jest.mocked(isGrafanaDevMode).mockReturnValue(false); config.apps = { [pluginId]: appPluginConfig, @@ -282,7 +292,7 @@ describe('ExposedComponentsRegistry', () => { }); registry.register({ - pluginId: 'grafana-basic-app2', + pluginId: 'grafana-basic-app1', configs: [ { id: 'grafana-basic-app1/hello-world/v1', // incorrectly scoped @@ -293,8 +303,8 @@ describe('ExposedComponentsRegistry', () => { ], }); - expect(consoleWarn).toHaveBeenCalledWith( - "[Plugin Extensions] Could not register exposed component with id 'grafana-basic-app1/hello-world/v1'. Reason: The component id does not match the id naming convention. Id should be prefixed with plugin id. e.g 'myorg-basic-app/my-component-id/v1'." + expect(log.error).toHaveBeenCalledWith( + "Could not register exposed component with 'grafana-basic-app1/hello-world/v1'. Reason: An exposed component with the same id already exists." ); const currentState2 = await registry.getState(); expect(Object.keys(currentState2)).toHaveLength(1); @@ -314,14 +324,14 @@ describe('ExposedComponentsRegistry', () => { ], }); - expect(consoleWarn).toHaveBeenCalledWith( - "[Plugin Extensions] Could not register exposed component with id 'hello-world/v1'. Reason: The component id does not match the id naming convention. Id should be prefixed with plugin id. e.g 'myorg-basic-app/my-component-id/v1'." + expect(log.error).toHaveBeenCalledWith( + "Could not register exposed component with 'hello-world/v1'. Reason: The component id does not match the id naming convention. Id should be prefixed with plugin id. e.g 'myorg-basic-app/my-component-id/v1'." ); const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(0); }); - it('should log a warning when exposed component id is not suffixed with component version', async () => { + it('should log a error when exposed component id is not suffixed with component version', async () => { const registry = new ExposedComponentsRegistry(); registry.register({ pluginId: 'grafana-basic-app1', @@ -335,8 +345,8 @@ describe('ExposedComponentsRegistry', () => { ], }); - expect(consoleWarn).toHaveBeenCalledWith( - "[Plugin Extensions] Exposed component with id 'grafana-basic-app1/hello-world' does not match the convention. It's recommended to suffix the id with the component version. e.g 'myorg-basic-app/my-component-id/v1'." + expect(log.error).toHaveBeenCalledWith( + "Exposed component does not match the convention. It's recommended to suffix the id with the component version. e.g 'myorg-basic-app/my-component-id/v1'." ); const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(1); @@ -357,8 +367,8 @@ describe('ExposedComponentsRegistry', () => { ], }); - expect(consoleWarn).toHaveBeenCalledWith( - "[Plugin Extensions] Could not register exposed component with id 'grafana-basic-app/hello-world/v1'. Reason: Description is missing." + expect(log.error).toHaveBeenCalledWith( + "Could not register exposed component with id 'grafana-basic-app/hello-world/v1'. Reason: Description is missing." ); const currentState = await registry.getState(); @@ -380,8 +390,8 @@ describe('ExposedComponentsRegistry', () => { ], }); - expect(consoleWarn).toHaveBeenCalledWith( - "[Plugin Extensions] Could not register exposed component with id 'grafana-basic-app/hello-world/v1'. Reason: Title is missing." + expect(log.error).toHaveBeenCalledWith( + "Could not register exposed component with id 'grafana-basic-app/hello-world/v1'. Reason: Title is missing." ); const currentState = await registry.getState(); @@ -468,7 +478,7 @@ describe('ExposedComponentsRegistry', () => { const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(0); - expect(consoleWarn).toHaveBeenCalled(); + expect(log.warning).toHaveBeenCalled(); }); it('should register an exposed component added by a core Grafana in dev-mode even if the meta-info is missing', async () => { @@ -491,7 +501,7 @@ describe('ExposedComponentsRegistry', () => { const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(1); - expect(consoleWarn).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should register an exposed component added by a plugin in production mode even if the meta-info is missing', async () => { @@ -517,7 +527,7 @@ describe('ExposedComponentsRegistry', () => { const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(1); - expect(consoleWarn).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should register an exposed component added by a plugin in dev-mode if the meta-info is present', async () => { @@ -543,6 +553,6 @@ describe('ExposedComponentsRegistry', () => { const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(1); - expect(consoleWarn).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); }); diff --git a/public/app/features/plugins/extensions/registry/ExposedComponentsRegistry.ts b/public/app/features/plugins/extensions/registry/ExposedComponentsRegistry.ts index c84c89ab199..85f3776f5b9 100644 --- a/public/app/features/plugins/extensions/registry/ExposedComponentsRegistry.ts +++ b/public/app/features/plugins/extensions/registry/ExposedComponentsRegistry.ts @@ -2,7 +2,7 @@ import { ReplaySubject } from 'rxjs'; import { PluginExtensionExposedComponentConfig } from '@grafana/data'; -import { isExposedComponentMetaInfoMissing, isGrafanaDevMode, logWarning } from '../utils'; +import { isExposedComponentMetaInfoMissing, isGrafanaDevMode } from '../utils'; import { extensionPointEndsWithVersion } from '../validators'; import { Registry, RegistryType, PluginExtensionConfigs } from './Registry'; @@ -37,41 +37,53 @@ export class ExposedComponentsRegistry extends Registry< for (const config of configs) { const { id, description, title } = config; + const pointIdLog = this.logger.child({ + extensionPointId: id, + description, + title, + pluginId, + }); if (!id.startsWith(pluginId)) { - logWarning( - `Could not register exposed component with id '${id}'. Reason: The component id does not match the id naming convention. Id should be prefixed with plugin id. e.g 'myorg-basic-app/my-component-id/v1'.` + pointIdLog.error( + `Could not register exposed component with '${id}'. Reason: The component id does not match the id naming convention. Id should be prefixed with plugin id. e.g 'myorg-basic-app/my-component-id/v1'.` ); continue; } if (!extensionPointEndsWithVersion(id)) { - logWarning( - `Exposed component with id '${id}' does not match the convention. It's recommended to suffix the id with the component version. e.g 'myorg-basic-app/my-component-id/v1'.` + pointIdLog.error( + `Exposed component does not match the convention. It's recommended to suffix the id with the component version. e.g 'myorg-basic-app/my-component-id/v1'.` ); } if (registry[id]) { - logWarning( - `Could not register exposed component with id '${id}'. Reason: An exposed component with the same id already exists.` + pointIdLog.error( + `Could not register exposed component with '${id}'. Reason: An exposed component with the same id already exists.` ); continue; } if (!title) { - logWarning(`Could not register exposed component with id '${id}'. Reason: Title is missing.`); + pointIdLog.error(`Could not register exposed component with id '${id}'. Reason: Title is missing.`); continue; } if (!description) { - logWarning(`Could not register exposed component with id '${id}'. Reason: Description is missing.`); + pointIdLog.error(`Could not register exposed component with id '${id}'. Reason: Description is missing.`); continue; } - if (pluginId !== 'grafana' && isGrafanaDevMode() && isExposedComponentMetaInfoMissing(pluginId, config)) { + if ( + pluginId !== 'grafana' && + isGrafanaDevMode() && + isExposedComponentMetaInfoMissing(pluginId, config, pointIdLog) + ) { continue; } + pointIdLog.debug(`Exposed component from '${pluginId}' to '${id}'`); + registry[id] = { ...config, pluginId }; } diff --git a/public/app/features/plugins/extensions/registry/Registry.ts b/public/app/features/plugins/extensions/registry/Registry.ts index 33470990e37..b2fbfbc09d8 100644 --- a/public/app/features/plugins/extensions/registry/Registry.ts +++ b/public/app/features/plugins/extensions/registry/Registry.ts @@ -1,5 +1,6 @@ import { Observable, ReplaySubject, Subject, firstValueFrom, map, scan, startWith } from 'rxjs'; +import { ExtensionsLog, log } from '../logs/log'; import { deepFreeze } from '../utils'; export const MSG_CANNOT_REGISTER_READ_ONLY = 'Cannot register to a read-only registry'; @@ -19,6 +20,7 @@ export abstract class Registry { private isReadOnly: boolean; // This is the subject that receives extension configs for a loaded plugin. private resultSubject: Subject>; + protected logger: ExtensionsLog; // This is the subject that we expose. // (It will buffer the last value on the stream - the registry - and emit it to new subscribers immediately.) protected registrySubject: ReplaySubject>; @@ -26,8 +28,10 @@ export abstract class Registry { constructor(options: { registrySubject?: ReplaySubject>; initialState?: RegistryType; + log?: ExtensionsLog; }) { this.resultSubject = new Subject>(); + this.logger = options.log ?? log; this.isReadOnly = false; // If the registry subject (observable) is provided, it means that all the registry updates are taken care of outside of this class -> it is read-only. @@ -41,7 +45,7 @@ export abstract class Registry { this.registrySubject = new ReplaySubject>(1); this.resultSubject .pipe( - scan(this.mapToRegistry, options.initialState ?? {}), + scan(this.mapToRegistry.bind(this), options.initialState ?? {}), // Emit an empty registry to start the stream (it is only going to do it once during construction, and then just passes down the values) startWith(options.initialState ?? {}), map((registry) => deepFreeze(registry)) diff --git a/public/app/features/plugins/extensions/usePluginComponent.test.tsx b/public/app/features/plugins/extensions/usePluginComponent.test.tsx index 7cf467b5e38..365dd520e91 100644 --- a/public/app/features/plugins/extensions/usePluginComponent.test.tsx +++ b/public/app/features/plugins/extensions/usePluginComponent.test.tsx @@ -5,6 +5,8 @@ import { PluginContextProvider, PluginLoadingStrategy, PluginMeta, PluginType } import { config } from '@grafana/runtime'; import { ExtensionRegistriesProvider } from './ExtensionRegistriesContext'; +import { log } from './logs/log'; +import { resetLogMock } from './logs/testUtils'; import { setupPluginExtensionRegistries } from './registry/setup'; import { PluginExtensionRegistries } from './registry/types'; import { usePluginComponent } from './usePluginComponent'; @@ -30,11 +32,20 @@ jest.mock('./utils', () => ({ wrapWithPluginContext: jest.fn().mockImplementation((_, component: React.ReactNode) => component), })); +jest.mock('./logs/log', () => { + const { createLogMock } = jest.requireActual('./logs/testUtils'); + const original = jest.requireActual('./logs/log'); + + return { + ...original, + log: createLogMock(), + }; +}); + describe('usePluginComponent()', () => { let registries: PluginExtensionRegistries; let wrapper: ({ children }: { children: React.ReactNode }) => JSX.Element; let pluginMeta: PluginMeta; - let consoleWarnSpy: jest.SpyInstance; const originalApps = config.apps; const pluginId = 'myorg-extensions-app'; const exposedComponentId = `${pluginId}/exposed-component/v1`; @@ -74,7 +85,7 @@ describe('usePluginComponent()', () => { beforeEach(() => { registries = setupPluginExtensionRegistries(); jest.mocked(isGrafanaDevMode).mockReturnValue(false); - consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); + resetLogMock(log); jest.mocked(wrapWithPluginContext).mockClear(); @@ -221,7 +232,7 @@ describe('usePluginComponent()', () => { // (No restrictions due to isGrafanaDevMode() = false) let { result } = renderHook(() => usePluginComponent(exposedComponentId), { wrapper }); expect(result.current.component).not.toBe(null); - expect(consoleWarnSpy).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should not validate the meta-info in core Grafana', () => { @@ -245,7 +256,7 @@ describe('usePluginComponent()', () => { }); expect(result.current.component).not.toBe(null); - expect(consoleWarnSpy).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should validate the meta-info in dev mode and if inside a plugin', () => { @@ -277,7 +288,7 @@ describe('usePluginComponent()', () => { // Shouldn't return the component, as it's not present in the plugin.json dependencies let { result } = renderHook(() => usePluginComponent(exposedComponentId), { wrapper }); expect(result.current.component).toBe(null); - expect(consoleWarnSpy).toHaveBeenCalled(); + expect(log.warning).toHaveBeenCalled(); }); it('should return the exposed component if the meta-info is correct and in dev mode', () => { @@ -307,6 +318,6 @@ describe('usePluginComponent()', () => { let { result } = renderHook(() => usePluginComponent(exposedComponentId), { wrapper }); expect(result.current.component).not.toBe(null); - expect(consoleWarnSpy).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); }); diff --git a/public/app/features/plugins/extensions/usePluginComponent.tsx b/public/app/features/plugins/extensions/usePluginComponent.tsx index 9ae43c27bbf..a32b6aa974c 100644 --- a/public/app/features/plugins/extensions/usePluginComponent.tsx +++ b/public/app/features/plugins/extensions/usePluginComponent.tsx @@ -2,9 +2,10 @@ import { useMemo } from 'react'; import { useObservable } from 'react-use'; import { usePluginContext } from '@grafana/data'; -import { logWarning, UsePluginComponentResult } from '@grafana/runtime'; +import { UsePluginComponentResult } from '@grafana/runtime'; import { useExposedComponentsRegistry } from './ExtensionRegistriesContext'; +import { log } from './logs/log'; import { isExposedComponentDependencyMissing, isGrafanaDevMode, wrapWithPluginContext } from './utils'; // Returns a component exposed by a plugin. @@ -18,16 +19,6 @@ export function usePluginComponent(id: string): UsePl // For backwards compatibility we don't enable restrictions in production or when the hook is used in core Grafana. const enableRestrictions = isGrafanaDevMode() && pluginContext; - if (enableRestrictions && isExposedComponentDependencyMissing(id, pluginContext)) { - logWarning( - `usePluginComponent("${id}") - The exposed component ("${id}") is missing from the dependencies[] in the "plugin.json" file.` - ); - return { - isLoading: false, - component: null, - }; - } - if (!registryState?.[id]) { return { isLoading: false, @@ -36,10 +27,25 @@ export function usePluginComponent(id: string): UsePl } const registryItem = registryState[id]; + const componentLog = log.child({ + title: registryItem.title, + description: registryItem.description, + pluginId: registryItem.pluginId, + }); + + if (enableRestrictions && isExposedComponentDependencyMissing(id, pluginContext, componentLog)) { + componentLog.warning( + `usePluginComponent("${id}") - The exposed component ("${id}") is missing from the dependencies[] in the "plugin.json" file.` + ); + return { + isLoading: false, + component: null, + }; + } return { isLoading: false, - component: wrapWithPluginContext(registryItem.pluginId, registryItem.component), + component: wrapWithPluginContext(registryItem.pluginId, registryItem.component, componentLog), }; }, [id, pluginContext, registryState]); } diff --git a/public/app/features/plugins/extensions/usePluginComponents.test.tsx b/public/app/features/plugins/extensions/usePluginComponents.test.tsx index 0c37e90e549..d174a871cc7 100644 --- a/public/app/features/plugins/extensions/usePluginComponents.test.tsx +++ b/public/app/features/plugins/extensions/usePluginComponents.test.tsx @@ -4,6 +4,8 @@ import { renderHook } from '@testing-library/react-hooks'; import { PluginContextProvider, PluginMeta, PluginType } from '@grafana/data'; import { ExtensionRegistriesProvider } from './ExtensionRegistriesContext'; +import { log } from './logs/log'; +import { resetLogMock } from './logs/testUtils'; import { setupPluginExtensionRegistries } from './registry/setup'; import { PluginExtensionRegistries } from './registry/types'; import { usePluginComponents } from './usePluginComponents'; @@ -29,18 +31,27 @@ jest.mock('./utils', () => ({ wrapWithPluginContext: jest.fn().mockImplementation((_, component: React.ReactNode) => component), })); +jest.mock('./logs/log', () => { + const { createLogMock } = jest.requireActual('./logs/testUtils'); + const original = jest.requireActual('./logs/log'); + + return { + ...original, + log: createLogMock(), + }; +}); + describe('usePluginComponents()', () => { let registries: PluginExtensionRegistries; let wrapper: ({ children }: { children: React.ReactNode }) => JSX.Element; let pluginMeta: PluginMeta; - let consoleWarnSpy: jest.SpyInstance; const pluginId = 'myorg-extensions-app'; const extensionPointId = `${pluginId}/extension-point/v1`; beforeEach(() => { jest.mocked(isGrafanaDevMode).mockReturnValue(false); + resetLogMock(log); registries = setupPluginExtensionRegistries(); - consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); jest.mocked(wrapWithPluginContext).mockClear(); @@ -251,7 +262,7 @@ describe('usePluginComponents()', () => { // (No restrictions due to isGrafanaDevMode() = false) let { result } = renderHook(() => usePluginComponents({ extensionPointId }), { wrapper }); expect(result.current.components.length).toBe(1); - expect(consoleWarnSpy).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should not validate the extension point id in production mode', () => { @@ -276,7 +287,7 @@ describe('usePluginComponents()', () => { wrapper, }); expect(result.current.components.length).toBe(0); - expect(consoleWarnSpy).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should not validate the extension point meta-info if used in Grafana core (no plugin context)', () => { @@ -305,7 +316,7 @@ describe('usePluginComponents()', () => { wrapper, }); expect(result.current.components.length).toBe(1); - expect(consoleWarnSpy).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should not validate the extension point id if used in Grafana core (no plugin context)', () => { @@ -321,7 +332,7 @@ describe('usePluginComponents()', () => { wrapper, }); expect(result.current.components.length).toBe(0); - expect(consoleWarnSpy).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should validate if the extension point meta-info is correct if in dev-mode and used by a plugin', () => { @@ -359,7 +370,7 @@ describe('usePluginComponents()', () => { // Trying to render an extension point that is not defined in the plugin meta let { result } = renderHook(() => usePluginComponents({ extensionPointId }), { wrapper }); expect(result.current.components.length).toBe(0); - expect(consoleWarnSpy).toHaveBeenCalled(); + expect(log.warning).toHaveBeenCalled(); }); it('should not log a warning if the extension point meta-info is correct if in dev-mode and used by a plugin', () => { @@ -403,6 +414,6 @@ describe('usePluginComponents()', () => { // Trying to render an extension point that is not defined in the plugin meta let { result } = renderHook(() => usePluginComponents({ extensionPointId }), { wrapper }); expect(result.current.components.length).toBe(0); - expect(consoleWarnSpy).toHaveBeenCalled(); + expect(log.warning).toHaveBeenCalled(); }); }); diff --git a/public/app/features/plugins/extensions/usePluginComponents.tsx b/public/app/features/plugins/extensions/usePluginComponents.tsx index 92dbc4e676c..45a6c6d9487 100644 --- a/public/app/features/plugins/extensions/usePluginComponents.tsx +++ b/public/app/features/plugins/extensions/usePluginComponents.tsx @@ -8,7 +8,8 @@ import { } from '@grafana/runtime/src/services/pluginExtensions/getPluginExtensions'; import { useAddedComponentsRegistry } from './ExtensionRegistriesContext'; -import { isExtensionPointMetaInfoMissing, isGrafanaDevMode, logWarning } from './utils'; +import { log } from './logs/log'; +import { isExtensionPointMetaInfoMissing, isGrafanaDevMode } from './utils'; import { isExtensionPointIdValid } from './validators'; // Returns an array of component extensions for the given extension point @@ -26,9 +27,13 @@ export function usePluginComponents({ const components: Array> = []; const extensionsByPlugin: Record = {}; const pluginId = pluginContext?.meta.id ?? ''; + const pointLog = log.child({ + pluginId, + extensionPointId, + }); if (enableRestrictions && !isExtensionPointIdValid({ extensionPointId, pluginId })) { - logWarning( + pointLog.warning( `Extension point usePluginComponents("${extensionPointId}") - the id should be prefixed with your plugin id ("${pluginId}/").` ); return { @@ -37,8 +42,8 @@ export function usePluginComponents({ }; } - if (enableRestrictions && isExtensionPointMetaInfoMissing(extensionPointId, pluginContext)) { - logWarning( + if (enableRestrictions && isExtensionPointMetaInfoMissing(extensionPointId, pluginContext, pointLog)) { + pointLog.warning( `usePluginComponents("${extensionPointId}") - The extension point is missing from the "plugin.json" file.` ); return { diff --git a/public/app/features/plugins/extensions/usePluginExtensions.tsx b/public/app/features/plugins/extensions/usePluginExtensions.tsx index 8b5c3e69f37..2ab882108de 100644 --- a/public/app/features/plugins/extensions/usePluginExtensions.tsx +++ b/public/app/features/plugins/extensions/usePluginExtensions.tsx @@ -6,8 +6,9 @@ import { GetPluginExtensionsOptions, UsePluginExtensionsResult } from '@grafana/ import { useSidecar } from 'app/core/context/SidecarContext'; import { getPluginExtensions } from './getPluginExtensions'; +import { log } from './logs/log'; import { PluginExtensionRegistries } from './registry/types'; -import { isExtensionPointMetaInfoMissing, isGrafanaDevMode, logWarning } from './utils'; +import { isExtensionPointMetaInfoMissing, isGrafanaDevMode } from './utils'; import { isExtensionPointIdValid } from './validators'; export function createUsePluginExtensions(registries: PluginExtensionRegistries) { @@ -25,13 +26,17 @@ export function createUsePluginExtensions(registries: PluginExtensionRegistries) // For backwards compatibility we don't enable restrictions in production or when the hook is used in core Grafana. const enableRestrictions = isGrafanaDevMode() && pluginContext !== null; const pluginId = pluginContext?.meta.id ?? ''; + const pointLog = log.child({ + pluginId, + extensionPointId, + }); if (!addedLinksRegistry && !addedComponentsRegistry) { return { extensions: [], isLoading: false }; } if (enableRestrictions && !isExtensionPointIdValid({ extensionPointId, pluginId })) { - logWarning( + pointLog.warning( `Extension point usePluginExtensions("${extensionPointId}") - the id should be prefixed with your plugin id ("${pluginId}/").` ); return { @@ -40,8 +45,8 @@ export function createUsePluginExtensions(registries: PluginExtensionRegistries) }; } - if (enableRestrictions && isExtensionPointMetaInfoMissing(extensionPointId, pluginContext)) { - logWarning( + if (enableRestrictions && isExtensionPointMetaInfoMissing(extensionPointId, pluginContext, pointLog)) { + pointLog.warning( `Invalid extension point. Reason: The extension point is not declared in the "plugin.json" file. ExtensionPointId: "${extensionPointId}"` ); return { diff --git a/public/app/features/plugins/extensions/usePluginLinks.test.tsx b/public/app/features/plugins/extensions/usePluginLinks.test.tsx index d4d0cc6a843..8fd92bd944a 100644 --- a/public/app/features/plugins/extensions/usePluginLinks.test.tsx +++ b/public/app/features/plugins/extensions/usePluginLinks.test.tsx @@ -4,6 +4,8 @@ import { renderHook } from '@testing-library/react-hooks'; import { PluginContextProvider, PluginMeta, PluginType } from '@grafana/data'; import { ExtensionRegistriesProvider } from './ExtensionRegistriesContext'; +import { log } from './logs/log'; +import { resetLogMock } from './logs/testUtils'; import { setupPluginExtensionRegistries } from './registry/setup'; import { PluginExtensionRegistries } from './registry/types'; import { usePluginLinks } from './usePluginLinks'; @@ -28,18 +30,27 @@ jest.mock('./utils', () => ({ isGrafanaDevMode: jest.fn().mockReturnValue(false), })); +jest.mock('./logs/log', () => { + const { createLogMock } = jest.requireActual('./logs/testUtils'); + const original = jest.requireActual('./logs/log'); + + return { + ...original, + log: createLogMock(), + }; +}); + describe('usePluginLinks()', () => { let registries: PluginExtensionRegistries; let wrapper: ({ children }: { children: React.ReactNode }) => JSX.Element; let pluginMeta: PluginMeta; - let consoleWarnSpy: jest.SpyInstance; const pluginId = 'myorg-extensions-app'; const extensionPointId = `${pluginId}/extension-point/v1`; beforeEach(() => { jest.mocked(isGrafanaDevMode).mockReturnValue(false); registries = setupPluginExtensionRegistries(); - consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); + resetLogMock(log); pluginMeta = { id: pluginId, @@ -194,7 +205,7 @@ describe('usePluginLinks()', () => { // (No restrictions due to isGrafanaDevMode() = false) let { result } = renderHook(() => usePluginLinks({ extensionPointId }), { wrapper }); expect(result.current.links.length).toBe(1); - expect(consoleWarnSpy).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should not validate the extension point id in production mode', () => { @@ -217,7 +228,7 @@ describe('usePluginLinks()', () => { // (No restrictions due to isGrafanaDevMode() = false) let { result } = renderHook(() => usePluginLinks({ extensionPointId: 'invalid-extension-point-id' }), { wrapper }); expect(result.current.links.length).toBe(0); - expect(consoleWarnSpy).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should not validate the extension point meta-info if used in Grafana core (no plugin context)', () => { @@ -244,7 +255,7 @@ describe('usePluginLinks()', () => { let { result } = renderHook(() => usePluginLinks({ extensionPointId: 'grafana/extension-point/v1' }), { wrapper }); expect(result.current.links.length).toBe(1); - expect(consoleWarnSpy).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should not validate the extension point id if used in Grafana core (no plugin context)', () => { @@ -258,7 +269,7 @@ describe('usePluginLinks()', () => { let { result } = renderHook(() => usePluginLinks({ extensionPointId: 'invalid-extension-point-id' }), { wrapper }); expect(result.current.links.length).toBe(0); - expect(consoleWarnSpy).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should validate if the extension point meta-info is correct if in dev-mode and used by a plugin', () => { @@ -296,7 +307,7 @@ describe('usePluginLinks()', () => { // Trying to render an extension point that is not defined in the plugin meta let { result } = renderHook(() => usePluginLinks({ extensionPointId }), { wrapper }); expect(result.current.links.length).toBe(0); - expect(consoleWarnSpy).toHaveBeenCalled(); + expect(log.warning).toHaveBeenCalled(); }); it('should not log a warning if the extension point meta-info is correct if in dev-mode and used by a plugin', () => { @@ -334,6 +345,6 @@ describe('usePluginLinks()', () => { // Trying to render an extension point that is not defined in the plugin meta let { result } = renderHook(() => usePluginLinks({ extensionPointId }), { wrapper }); expect(result.current.links.length).toBe(0); - expect(consoleWarnSpy).toHaveBeenCalled(); + expect(log.warning).toHaveBeenCalled(); }); }); diff --git a/public/app/features/plugins/extensions/usePluginLinks.tsx b/public/app/features/plugins/extensions/usePluginLinks.tsx index 1f868b01c77..bd9bba9bcc9 100644 --- a/public/app/features/plugins/extensions/usePluginLinks.tsx +++ b/public/app/features/plugins/extensions/usePluginLinks.tsx @@ -9,6 +9,7 @@ import { } from '@grafana/runtime/src/services/pluginExtensions/getPluginExtensions'; import { useAddedLinksRegistry } from './ExtensionRegistriesContext'; +import { log } from './logs/log'; import { generateExtensionId, getLinkExtensionOnClick, @@ -17,7 +18,6 @@ import { getReadOnlyProxy, isExtensionPointMetaInfoMissing, isGrafanaDevMode, - logWarning, } from './utils'; import { isExtensionPointIdValid } from './validators'; @@ -35,9 +35,13 @@ export function usePluginLinks({ // For backwards compatibility we don't enable restrictions in production or when the hook is used in core Grafana. const enableRestrictions = isGrafanaDevMode() && pluginContext !== null; const pluginId = pluginContext?.meta.id ?? ''; + const pointLog = log.child({ + pluginId, + extensionPointId, + }); if (enableRestrictions && !isExtensionPointIdValid({ extensionPointId, pluginId })) { - logWarning( + pointLog.warning( `Extension point usePluginLinks("${extensionPointId}") - the id should be prefixed with your plugin id ("${pluginId}/").` ); return { @@ -46,8 +50,8 @@ export function usePluginLinks({ }; } - if (enableRestrictions && isExtensionPointMetaInfoMissing(extensionPointId, pluginContext)) { - logWarning( + if (enableRestrictions && isExtensionPointMetaInfoMissing(extensionPointId, pluginContext, pointLog)) { + pointLog.warning( `Invalid extension point. Reason: The extension point is not declared in the "plugin.json" file. ExtensionPointId: "${extensionPointId}"` ); return { @@ -78,8 +82,14 @@ export function usePluginLinks({ extensionsByPlugin[pluginId] = 0; } + const linkLog = pointLog.child({ + path: addedLink.path ?? '', + title: addedLink.title, + description: addedLink.description, + onClick: typeof addedLink.onClick, + }); // Run the configure() function with the current context, and apply the ovverides - const overrides = getLinkExtensionOverrides(pluginId, addedLink, frozenContext); + const overrides = getLinkExtensionOverrides(pluginId, addedLink, linkLog, frozenContext); // configure() returned an `undefined` -> hide the extension if (addedLink.configure && overrides === undefined) { @@ -91,7 +101,7 @@ export function usePluginLinks({ id: generateExtensionId(pluginId, extensionPointId, addedLink.title), type: PluginExtensionTypes.link, pluginId: pluginId, - onClick: getLinkExtensionOnClick(pluginId, extensionPointId, addedLink, frozenContext), + onClick: getLinkExtensionOnClick(pluginId, extensionPointId, addedLink, linkLog, frozenContext), // Configurable properties icon: overrides?.icon || addedLink.icon, diff --git a/public/app/features/plugins/extensions/utils.test.tsx b/public/app/features/plugins/extensions/utils.test.tsx index 3697fe0b71f..c090e2ac232 100644 --- a/public/app/features/plugins/extensions/utils.test.tsx +++ b/public/app/features/plugins/extensions/utils.test.tsx @@ -13,6 +13,8 @@ import { config } from '@grafana/runtime'; import appEvents from 'app/core/app_events'; import { ShowModalReactEvent } from 'app/types/events'; +import { log } from './logs/log'; +import { createLogMock } from './logs/testUtils'; import { deepFreeze, handleErrorsInFn, @@ -441,7 +443,7 @@ describe('Plugin Extensions / Utils', () => { it('should make the plugin context available for the wrapped component', async () => { const pluginId = 'grafana-worldmap-panel'; - const Component = wrapWithPluginContext(pluginId, ExampleComponent); + const Component = wrapWithPluginContext(pluginId, ExampleComponent, log); render(); @@ -451,7 +453,7 @@ describe('Plugin Extensions / Utils', () => { it('should pass the properties into the wrapped component', async () => { const pluginId = 'grafana-worldmap-panel'; - const Component = wrapWithPluginContext(pluginId, ExampleComponent); + const Component = wrapWithPluginContext(pluginId, ExampleComponent, log); render(); @@ -461,7 +463,6 @@ describe('Plugin Extensions / Utils', () => { }); describe('isAddedLinkMetaInfoMissing()', () => { - let consoleWarnSpy: jest.SpyInstance; const originalApps = config.apps; const pluginId = 'myorg-extensions-app'; const appPluginConfig = { @@ -495,7 +496,6 @@ describe('Plugin Extensions / Utils', () => { }; beforeEach(() => { - consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); config.apps = { [pluginId]: appPluginConfig, }; @@ -506,63 +506,75 @@ describe('Plugin Extensions / Utils', () => { }); it('should return FALSE if the meta-info in the plugin.json is correct', () => { + const log = createLogMock(); config.apps[pluginId].extensions.addedLinks.push(extensionConfig); - const returnValue = isAddedLinkMetaInfoMissing(pluginId, extensionConfig); + const returnValue = isAddedLinkMetaInfoMissing(pluginId, extensionConfig, log); expect(returnValue).toBe(false); - expect(consoleWarnSpy).toHaveBeenCalledTimes(0); + expect(log.warning).toHaveBeenCalledTimes(0); }); it('should return TRUE and log a warning if the app config is not found', () => { + const log = createLogMock(); delete config.apps[pluginId]; - const returnValue = isAddedLinkMetaInfoMissing(pluginId, extensionConfig); + const returnValue = isAddedLinkMetaInfoMissing(pluginId, extensionConfig, log); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch("couldn't find app plugin"); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch("couldn't find app plugin"); }); it('should return TRUE and log a warning if the link has no meta-info in the plugin.json', () => { + const log = createLogMock(); config.apps[pluginId].extensions.addedLinks = []; - const returnValue = isAddedLinkMetaInfoMissing(pluginId, extensionConfig); + const returnValue = isAddedLinkMetaInfoMissing(pluginId, extensionConfig, log); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch('not registered in the plugin.json'); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch('not registered in the plugin.json'); }); it('should return TRUE and log a warning if the "targets" do not match', () => { + const log = createLogMock(); config.apps[pluginId].extensions.addedLinks.push(extensionConfig); - const returnValue = isAddedLinkMetaInfoMissing(pluginId, { - ...extensionConfig, - targets: [PluginExtensionPoints.DashboardPanelMenu, PluginExtensionPoints.ExploreToolbarAction], - }); + const returnValue = isAddedLinkMetaInfoMissing( + pluginId, + { + ...extensionConfig, + targets: [PluginExtensionPoints.DashboardPanelMenu, PluginExtensionPoints.ExploreToolbarAction], + }, + log + ); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch('"targets" don\'t match'); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch('"targets" don\'t match'); }); it('should return TRUE and log a warning if the "description" does not match', () => { + const log = createLogMock(); config.apps[pluginId].extensions.addedLinks.push(extensionConfig); - const returnValue = isAddedLinkMetaInfoMissing(pluginId, { - ...extensionConfig, - description: 'Link description UPDATED', - }); + const returnValue = isAddedLinkMetaInfoMissing( + pluginId, + { + ...extensionConfig, + description: 'Link description UPDATED', + }, + log + ); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch('"description" doesn\'t match'); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch('"description" doesn\'t match'); }); }); describe('isAddedComponentMetaInfoMissing()', () => { - let consoleWarnSpy: jest.SpyInstance; const originalApps = config.apps; const pluginId = 'myorg-extensions-app'; const appPluginConfig = { @@ -597,7 +609,6 @@ describe('Plugin Extensions / Utils', () => { }; beforeEach(() => { - consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); config.apps = { [pluginId]: appPluginConfig, }; @@ -608,63 +619,75 @@ describe('Plugin Extensions / Utils', () => { }); it('should return FALSE if the meta-info in the plugin.json is correct', () => { + const log = createLogMock(); config.apps[pluginId].extensions.addedComponents.push(extensionConfig); - const returnValue = isAddedComponentMetaInfoMissing(pluginId, extensionConfig); + const returnValue = isAddedComponentMetaInfoMissing(pluginId, extensionConfig, log); expect(returnValue).toBe(false); - expect(consoleWarnSpy).toHaveBeenCalledTimes(0); + expect(log.warning).toHaveBeenCalledTimes(0); }); it('should return TRUE and log a warning if the app config is not found', () => { + const log = createLogMock(); delete config.apps[pluginId]; - const returnValue = isAddedComponentMetaInfoMissing(pluginId, extensionConfig); + const returnValue = isAddedComponentMetaInfoMissing(pluginId, extensionConfig, log); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch("couldn't find app plugin"); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch("couldn't find app plugin"); }); it('should return TRUE and log a warning if the Component has no meta-info in the plugin.json', () => { + const log = createLogMock(); config.apps[pluginId].extensions.addedComponents = []; - const returnValue = isAddedComponentMetaInfoMissing(pluginId, extensionConfig); + const returnValue = isAddedComponentMetaInfoMissing(pluginId, extensionConfig, log); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch('not registered in the plugin.json'); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch('not registered in the plugin.json'); }); it('should return TRUE and log a warning if the "targets" do not match', () => { + const log = createLogMock(); config.apps[pluginId].extensions.addedComponents.push(extensionConfig); - const returnValue = isAddedComponentMetaInfoMissing(pluginId, { - ...extensionConfig, - targets: [PluginExtensionPoints.ExploreToolbarAction], - }); + const returnValue = isAddedComponentMetaInfoMissing( + pluginId, + { + ...extensionConfig, + targets: [PluginExtensionPoints.ExploreToolbarAction], + }, + log + ); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch('"targets" don\'t match'); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch('"targets" don\'t match'); }); it('should return TRUE and log a warning if the "description" does not match', () => { + const log = createLogMock(); config.apps[pluginId].extensions.addedComponents.push(extensionConfig); - const returnValue = isAddedComponentMetaInfoMissing(pluginId, { - ...extensionConfig, - description: 'UPDATED', - }); + const returnValue = isAddedComponentMetaInfoMissing( + pluginId, + { + ...extensionConfig, + description: 'UPDATED', + }, + log + ); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch('"description" doesn\'t match'); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch('"description" doesn\'t match'); }); }); describe('isExposedComponentMetaInfoMissing()', () => { - let consoleWarnSpy: jest.SpyInstance; const originalApps = config.apps; const pluginId = 'myorg-extensions-app'; const appPluginConfig = { @@ -699,7 +722,6 @@ describe('Plugin Extensions / Utils', () => { }; beforeEach(() => { - consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); config.apps = { [pluginId]: appPluginConfig, }; @@ -710,69 +732,80 @@ describe('Plugin Extensions / Utils', () => { }); it('should return FALSE if the meta-info in the plugin.json is correct', () => { + const log = createLogMock(); config.apps[pluginId].extensions.exposedComponents.push(exposedComponentConfig); - const returnValue = isExposedComponentMetaInfoMissing(pluginId, exposedComponentConfig); + const returnValue = isExposedComponentMetaInfoMissing(pluginId, exposedComponentConfig, log); expect(returnValue).toBe(false); - expect(consoleWarnSpy).toHaveBeenCalledTimes(0); + expect(log.warning).toHaveBeenCalledTimes(0); }); it('should return TRUE and log a warning if the app config is not found', () => { + const log = createLogMock(); delete config.apps[pluginId]; - const returnValue = isExposedComponentMetaInfoMissing(pluginId, exposedComponentConfig); + const returnValue = isExposedComponentMetaInfoMissing(pluginId, exposedComponentConfig, log); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch("couldn't find app plugin"); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch("couldn't find app plugin"); }); it('should return TRUE and log a warning if the exposed component has no meta-info in the plugin.json', () => { + const log = createLogMock(); config.apps[pluginId].extensions.exposedComponents = []; - const returnValue = isExposedComponentMetaInfoMissing(pluginId, exposedComponentConfig); + const returnValue = isExposedComponentMetaInfoMissing(pluginId, exposedComponentConfig, log); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch('not registered in the plugin.json'); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch('not registered in the plugin.json'); }); it('should return TRUE and log a warning if the title does not match', () => { + const log = createLogMock(); config.apps[pluginId].extensions.exposedComponents.push(exposedComponentConfig); - const returnValue = isExposedComponentMetaInfoMissing(pluginId, { - ...exposedComponentConfig, - title: 'UPDATED', - }); + const returnValue = isExposedComponentMetaInfoMissing( + pluginId, + { + ...exposedComponentConfig, + title: 'UPDATED', + }, + log + ); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch('"title" doesn\'t match'); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch('"title" doesn\'t match'); }); it('should return TRUE and log a warning if the "description" does not match', () => { + const log = createLogMock(); config.apps[pluginId].extensions.exposedComponents.push(exposedComponentConfig); - const returnValue = isExposedComponentMetaInfoMissing(pluginId, { - ...exposedComponentConfig, - description: 'UPDATED', - }); + const returnValue = isExposedComponentMetaInfoMissing( + pluginId, + { + ...exposedComponentConfig, + description: 'UPDATED', + }, + log + ); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch('"description" doesn\'t match'); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch('"description" doesn\'t match'); }); }); describe('isExposedComponentDependencyMissing()', () => { - let consoleWarnSpy: jest.SpyInstance; let pluginContext: PluginContextType; const pluginId = 'myorg-extensions-app'; const exposedComponentId = `${pluginId}/component/v1`; beforeEach(() => { - consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); pluginContext = { meta: { id: pluginId, @@ -806,35 +839,37 @@ describe('Plugin Extensions / Utils', () => { }); it('should return FALSE if the meta-info in the plugin.json is correct', () => { + const log = createLogMock(); pluginContext.meta.dependencies?.extensions.exposedComponents.push(exposedComponentId); - const returnValue = isExposedComponentDependencyMissing(exposedComponentId, pluginContext); + const returnValue = isExposedComponentDependencyMissing(exposedComponentId, pluginContext, log); expect(returnValue).toBe(false); - expect(consoleWarnSpy).toHaveBeenCalledTimes(0); + expect(log.warning).toHaveBeenCalledTimes(0); }); it('should return TRUE and log a warning if the dependencies are missing', () => { + const log = createLogMock(); delete pluginContext.meta.dependencies; - const returnValue = isExposedComponentDependencyMissing(exposedComponentId, pluginContext); + const returnValue = isExposedComponentDependencyMissing(exposedComponentId, pluginContext, log); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch(`Using exposed component "${exposedComponentId}"`); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch(`Using exposed component "${exposedComponentId}"`); }); it('should return TRUE and log a warning if the exposed component id is not specified in the list of dependencies', () => { - const returnValue = isExposedComponentDependencyMissing(exposedComponentId, pluginContext); + const log = createLogMock(); + const returnValue = isExposedComponentDependencyMissing(exposedComponentId, pluginContext, log); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch(`Using exposed component "${exposedComponentId}"`); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch(`Using exposed component "${exposedComponentId}"`); }); }); describe('isExtensionPointMetaInfoMissing()', () => { - let consoleWarnSpy: jest.SpyInstance; let pluginContext: PluginContextType; const pluginId = 'myorg-extensions-app'; const extensionPointId = `${pluginId}/extension-point/v1`; @@ -845,7 +880,6 @@ describe('Plugin Extensions / Utils', () => { }; beforeEach(() => { - consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); pluginContext = { meta: { id: pluginId, @@ -885,20 +919,22 @@ describe('Plugin Extensions / Utils', () => { }); it('should return FALSE if the meta-info in the plugin.json is correct', () => { + const log = createLogMock(); pluginContext.meta.extensions?.extensionPoints.push(extensionPointConfig); - const returnValue = isExtensionPointMetaInfoMissing(extensionPointId, pluginContext); + const returnValue = isExtensionPointMetaInfoMissing(extensionPointId, pluginContext, log); expect(returnValue).toBe(false); - expect(consoleWarnSpy).toHaveBeenCalledTimes(0); + expect(log.warning).toHaveBeenCalledTimes(0); }); it('should return TRUE and log a warning if the extension point id is not recorded in the plugin.json', () => { - const returnValue = isExtensionPointMetaInfoMissing(extensionPointId, pluginContext); + const log = createLogMock(); + const returnValue = isExtensionPointMetaInfoMissing(extensionPointId, pluginContext, log); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch(`Extension point "${extensionPointId}"`); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch(`Extension point "${extensionPointId}"`); }); }); }); diff --git a/public/app/features/plugins/extensions/utils.tsx b/public/app/features/plugins/extensions/utils.tsx index 0938c1df666..5f92e51b7f6 100644 --- a/public/app/features/plugins/extensions/utils.tsx +++ b/public/app/features/plugins/extensions/utils.tsx @@ -28,13 +28,10 @@ import { sidecarService } from 'app/core/services/SidecarService'; import { getPluginSettings } from 'app/features/plugins/pluginSettings'; import { ShowModalReactEvent } from 'app/types/events'; +import { ExtensionsLog, log } from './logs/log'; import { AddedLinkRegistryItem } from './registry/AddedLinksRegistry'; import { assertIsNotPromise, assertLinkPathIsValid, assertStringProps, isPromise } from './validators'; -export function logWarning(message: string) { - console.warn(`[Plugin Extensions] ${message}`); -} - export function isPluginExtensionLinkConfig( extension: PluginExtensionConfig | undefined ): extension is PluginExtensionLinkConfig { @@ -59,7 +56,11 @@ export function createOpenModalFunction(pluginId: string): PluginExtensionEventH appEvents.publish( new ShowModalReactEvent({ - component: wrapWithPluginContext(pluginId, getModalWrapper({ title, body, width, height })), + component: wrapWithPluginContext( + pluginId, + getModalWrapper({ title, body, width, height }), + log + ), }) ); }; @@ -69,7 +70,7 @@ type ModalWrapperProps = { onDismiss: () => void; }; -export const wrapWithPluginContext = (pluginId: string, Component: React.ComponentType) => { +export const wrapWithPluginContext = (pluginId: string, Component: React.ComponentType, log: ExtensionsLog) => { const WrappedExtensionComponent = (props: T & React.JSX.IntrinsicAttributes) => { const { error, @@ -82,12 +83,15 @@ export const wrapWithPluginContext = (pluginId: string, Component: React.Com } if (error) { - logWarning(`Could not fetch plugin meta information for "${pluginId}", aborting. (${error.message})`); + log.error(`Could not fetch plugin meta information for "${pluginId}", aborting. (${error.message})`, { + stack: error.stack ?? '', + message: error.message, + }); return null; } if (!pluginMeta) { - logWarning(`Fetched plugin meta information is empty for "${pluginId}", aborting.`); + log.error(`Fetched plugin meta information is empty for "${pluginId}", aborting.`); return null; } @@ -298,7 +302,12 @@ export function createExtensionSubMenu(extensions: PluginExtensionLink[]): Panel return subMenu; } -export function getLinkExtensionOverrides(pluginId: string, config: AddedLinkRegistryItem, context?: object) { +export function getLinkExtensionOverrides( + pluginId: string, + config: AddedLinkRegistryItem, + log: ExtensionsLog, + context?: object +) { try { const overrides = config.configure?.(context, { isAppOpened: () => isAppOpened(pluginId) }); @@ -325,7 +334,7 @@ export function getLinkExtensionOverrides(pluginId: string, config: AddedLinkReg assertStringProps({ title, description }, ['title', 'description']); if (Object.keys(rest).length > 0) { - logWarning( + log.warning( `Extension "${config.title}", is trying to override restricted properties: ${Object.keys(rest).join( ', ' )} which will be ignored.` @@ -341,7 +350,10 @@ export function getLinkExtensionOverrides(pluginId: string, config: AddedLinkReg }; } catch (error) { if (error instanceof Error) { - logWarning(error.message); + log.error(`Failed to configure link with title "${config.title}"`, { + stack: error.stack ?? '', + message: error.message, + }); } // If there is an error, we hide the extension @@ -354,6 +366,7 @@ export function getLinkExtensionOnClick( pluginId: string, extensionPointId: string, config: AddedLinkRegistryItem, + log: ExtensionsLog, context?: object ): ((event?: React.MouseEvent) => void) | undefined { const { onClick } = config; @@ -379,18 +392,25 @@ export function getLinkExtensionOnClick( closeAppInSideview: () => closeAppInSideview(pluginId), }; + log.debug(`onClick '${config.title}' at '${extensionPointId}'`); const result = onClick(event, helpers); if (isPromise(result)) { - result.catch((e) => { - if (e instanceof Error) { - logWarning(e.message); + result.catch((error) => { + if (error instanceof Error) { + log.error(error.message, { + message: error.message, + stack: error.stack ?? '', + }); } }); } } catch (error) { if (error instanceof Error) { - logWarning(error.message); + log.error(error.message, { + message: error.message, + stack: error.stack ?? '', + }); } } }; @@ -417,12 +437,16 @@ export const isAppOpened = (pluginId: string) => sidecarService.isAppOpened(plug export const isGrafanaDevMode = () => config.buildInfo.env === 'development'; // Checks if the meta information is missing from the plugin's plugin.json file -export const isExtensionPointMetaInfoMissing = (extensionPointId: string, pluginContext: PluginContextType) => { +export const isExtensionPointMetaInfoMissing = ( + extensionPointId: string, + pluginContext: PluginContextType, + log: ExtensionsLog +) => { const pluginId = pluginContext.meta?.id; const extensionPoints = pluginContext.meta?.extensions?.extensionPoints; if (!extensionPoints || !extensionPoints.some((ep) => ep.id === extensionPointId)) { - logWarning( + log.warning( `Extension point "${extensionPointId}" - it's not recorded in the "plugin.json" for "${pluginId}". Please add it under "extensions.extensionPoints[]".` ); return true; @@ -432,12 +456,16 @@ export const isExtensionPointMetaInfoMissing = (extensionPointId: string, plugin }; // Checks if an exposed component that the plugin is depending on is missing from the `dependencies` in the plugin.json file -export const isExposedComponentDependencyMissing = (id: string, pluginContext: PluginContextType) => { +export const isExposedComponentDependencyMissing = ( + id: string, + pluginContext: PluginContextType, + log: ExtensionsLog +) => { const pluginId = pluginContext.meta?.id; const exposedComponentsDependencies = pluginContext.meta?.dependencies?.extensions?.exposedComponents; if (!exposedComponentsDependencies || !exposedComponentsDependencies.includes(id)) { - logWarning( + log.warning( `Using exposed component "${id}" - it's not recorded in the "plugin.json" for "${pluginId}". Please add it under "dependencies.extensions.exposedComponents[]".` ); return true; @@ -446,31 +474,35 @@ export const isExposedComponentDependencyMissing = (id: string, pluginContext: P return false; }; -export const isAddedLinkMetaInfoMissing = (pluginId: string, metaInfo: PluginExtensionAddedLinkConfig) => { +export const isAddedLinkMetaInfoMissing = ( + pluginId: string, + metaInfo: PluginExtensionAddedLinkConfig, + log: ExtensionsLog +) => { const app = config.apps[pluginId]; const logPrefix = `Added-link "${metaInfo.title}" from "${pluginId}" -`; const pluginJsonMetaInfo = app ? app.extensions.addedLinks.find(({ title }) => title === metaInfo.title) : null; if (!app) { - logWarning(`${logPrefix} couldn't find app plugin "${pluginId}"`); + log.warning(`${logPrefix} couldn't find app plugin "${pluginId}"`); return true; } if (!pluginJsonMetaInfo) { - logWarning(`${logPrefix} not registered in the plugin.json under "extensions.addedLinks[]".`); + log.warning(`${logPrefix} not registered in the plugin.json under "extensions.addedLinks[]".`); return true; } const targets = Array.isArray(metaInfo.targets) ? metaInfo.targets : [metaInfo.targets]; if (!targets.every((target) => pluginJsonMetaInfo.targets.includes(target))) { - logWarning(`${logPrefix} the "targets" don't match with ones in the plugin.json under "extensions.addedLinks[]".`); + log.warning(`${logPrefix} the "targets" don't match with ones in the plugin.json under "extensions.addedLinks[]".`); return true; } if (pluginJsonMetaInfo.description !== metaInfo.description) { - logWarning( + log.warning( `${logPrefix} the "description" doesn't match with one in the plugin.json under "extensions.addedLinks[]".` ); @@ -480,25 +512,29 @@ export const isAddedLinkMetaInfoMissing = (pluginId: string, metaInfo: PluginExt return false; }; -export const isAddedComponentMetaInfoMissing = (pluginId: string, metaInfo: PluginExtensionAddedComponentConfig) => { +export const isAddedComponentMetaInfoMissing = ( + pluginId: string, + metaInfo: PluginExtensionAddedComponentConfig, + log: ExtensionsLog +) => { const app = config.apps[pluginId]; const logPrefix = `Added component "${metaInfo.title}" -`; const pluginJsonMetaInfo = app ? app.extensions.addedComponents.find(({ title }) => title === metaInfo.title) : null; if (!app) { - logWarning(`${logPrefix} couldn't find app plugin "${pluginId}"`); + log.warning(`${logPrefix} couldn't find app plugin "${pluginId}"`); return true; } if (!pluginJsonMetaInfo) { - logWarning(`${logPrefix} not registered in the plugin.json under "extensions.addedComponents[]".`); + log.warning(`${logPrefix} not registered in the plugin.json under "extensions.addedComponents[]".`); return true; } const targets = Array.isArray(metaInfo.targets) ? metaInfo.targets : [metaInfo.targets]; if (!targets.every((target) => pluginJsonMetaInfo.targets.includes(target))) { - logWarning( + log.warning( `${logPrefix} the "targets" don't match with ones in the plugin.json under "extensions.addedComponents[]".` ); @@ -506,7 +542,7 @@ export const isAddedComponentMetaInfoMissing = (pluginId: string, metaInfo: Plug } if (pluginJsonMetaInfo.description !== metaInfo.description) { - logWarning( + log.warning( `${logPrefix} the "description" doesn't match with one in the plugin.json under "extensions.addedComponents[]".` ); @@ -518,25 +554,26 @@ export const isAddedComponentMetaInfoMissing = (pluginId: string, metaInfo: Plug export const isExposedComponentMetaInfoMissing = ( pluginId: string, - metaInfo: PluginExtensionExposedComponentConfig + metaInfo: PluginExtensionExposedComponentConfig, + log: ExtensionsLog ) => { const app = config.apps[pluginId]; const logPrefix = `Exposed component "${metaInfo.id}" -`; const pluginJsonMetaInfo = app ? app.extensions.exposedComponents.find(({ id }) => id === metaInfo.id) : null; if (!app) { - logWarning(`${logPrefix} couldn't find app plugin: "${pluginId}"`); + log.warning(`${logPrefix} couldn't find app plugin: "${pluginId}"`); return true; } if (!pluginJsonMetaInfo) { - logWarning(`${logPrefix} not registered in the plugin.json under "extensions.exposedComponents[]".`); + log.warning(`${logPrefix} not registered in the plugin.json under "extensions.exposedComponents[]".`); return true; } if (pluginJsonMetaInfo.title !== metaInfo.title) { - logWarning( + log.warning( `${logPrefix} the "title" doesn't match with one in the plugin.json under "extensions.exposedComponents[]".` ); @@ -544,7 +581,7 @@ export const isExposedComponentMetaInfoMissing = ( } if (pluginJsonMetaInfo.description !== metaInfo.description) { - logWarning( + log.warning( `${logPrefix} the "description" doesn't match with one in the plugin.json under "extensions.exposedComponents[]".` ); diff --git a/public/app/features/query-library/api/factory.ts b/public/app/features/query-library/api/factory.ts index 5ee9b021c59..d456fa4e453 100644 --- a/public/app/features/query-library/api/factory.ts +++ b/public/app/features/query-library/api/factory.ts @@ -7,7 +7,7 @@ import { baseQuery } from './query'; // Currently, we are loading all query templates // Organizations can have maximum of 1000 query templates -const GET_LIMIT = 1000; +export const QUERY_LIBRARY_GET_LIMIT = 1000; export const queryLibraryApi = createApi({ baseQuery, @@ -15,7 +15,7 @@ export const queryLibraryApi = createApi({ endpoints: (builder) => ({ allQueryTemplates: builder.query({ query: () => ({ - url: `?limit=${GET_LIMIT}`, + url: `?limit=${QUERY_LIBRARY_GET_LIMIT}`, }), transformResponse: convertDataQueryResponseToQueryTemplates, providesTags: ['QueryTemplatesList'], diff --git a/public/app/features/query/state/PanelQueryRunner.ts b/public/app/features/query/state/PanelQueryRunner.ts index 87bb9bf93f5..8853a9945a2 100644 --- a/public/app/features/query/state/PanelQueryRunner.ts +++ b/public/app/features/query/state/PanelQueryRunner.ts @@ -1,4 +1,4 @@ -import { cloneDeep, merge } from 'lodash'; +import { cloneDeep, merge, isEqual } from 'lodash'; import { Observable, of, ReplaySubject, Unsubscribable } from 'rxjs'; import { map, mergeMap, catchError } from 'rxjs/operators'; @@ -371,6 +371,7 @@ export class PanelQueryRunner { let sameSeries = compareArrayValues(last.series ?? [], next.series ?? [], (a, b) => a === b); let sameAnnotations = compareArrayValues(last.annotations ?? [], next.annotations ?? [], (a, b) => a === b); let sameState = last.state === next.state; + let sameErrors = compareArrayValues(last.errors ?? [], next.errors ?? [], (a, b) => isEqual(a, b)); if (sameSeries) { next.series = last.series; @@ -380,7 +381,7 @@ export class PanelQueryRunner { next.annotations = last.annotations; } - if (sameSeries && sameAnnotations && sameState) { + if (sameSeries && sameAnnotations && sameState && sameErrors) { return; } } diff --git a/public/app/features/sandbox/TestStuffPage.tsx b/public/app/features/sandbox/TestStuffPage.tsx index 7faa71465b7..36368f5b508 100644 --- a/public/app/features/sandbox/TestStuffPage.tsx +++ b/public/app/features/sandbox/TestStuffPage.tsx @@ -19,7 +19,6 @@ export const TestStuffPage = () => { - diff --git a/public/app/features/scopes/internal/ScopesDashboardsScene.tsx b/public/app/features/scopes/internal/ScopesDashboardsScene.tsx index e838286df3f..b5450af8630 100644 --- a/public/app/features/scopes/internal/ScopesDashboardsScene.tsx +++ b/public/app/features/scopes/internal/ScopesDashboardsScene.tsx @@ -1,5 +1,6 @@ import { css, cx } from '@emotion/css'; import { isEqual } from 'lodash'; +import { finalize, from, Subscription } from 'rxjs'; import { GrafanaTheme2, ScopeDashboardBinding } from '@grafana/data'; import { SceneComponentProps, SceneObjectBase, SceneObjectRef, SceneObjectState } from '@grafana/scenes'; @@ -10,7 +11,6 @@ import { ScopesDashboardsTree } from './ScopesDashboardsTree'; import { ScopesDashboardsTreeSearch } from './ScopesDashboardsTreeSearch'; import { ScopesSelectorScene } from './ScopesSelectorScene'; import { fetchDashboards } from './api'; -import { DASHBOARDS_OPENED_KEY } from './const'; import { SuggestedDashboardsFoldersMap } from './types'; import { filterFolders, getScopeNamesFromSelectedScopes, groupDashboards } from './utils'; @@ -26,6 +26,7 @@ export interface ScopesDashboardsSceneState extends SceneObjectState { isLoading: boolean; isPanelOpened: boolean; isEnabled: boolean; + isReadOnly: boolean; scopesSelected: boolean; searchQuery: string; } @@ -36,8 +37,9 @@ export const getInitialDashboardsState: () => Omit Omit { static Component = ScopesDashboardsSceneRenderer; + private dashboardsFetchingSub: Subscription | undefined; + constructor() { super({ selector: null, @@ -52,35 +56,44 @@ export class ScopesDashboardsScene extends SceneObjectBase { - if (this.state.isEnabled && this.state.isPanelOpened) { - this.fetchDashboards(); - } - const resolvedSelector = this.state.selector?.resolve(); + if (resolvedSelector?.state.scopes.length ?? 0 > 0) { + this.fetchDashboards(); + this.openPanel(); + } + if (resolvedSelector) { this._subs.add( resolvedSelector.subscribeToState((newState, prevState) => { - if ( - this.state.isEnabled && - this.state.isPanelOpened && - !newState.isLoadingScopes && - (prevState.isLoadingScopes || newState.scopes !== prevState.scopes) - ) { + const newScopeNames = getScopeNamesFromSelectedScopes(newState.scopes ?? []); + const oldScopeNames = getScopeNamesFromSelectedScopes(prevState.scopes ?? []); + + if (!isEqual(newScopeNames, oldScopeNames)) { this.fetchDashboards(); + + if (newState.scopes.length > 0) { + this.openPanel(); + } else { + this.closePanel(); + } } }) ); } + + return () => { + this.dashboardsFetchingSub?.unsubscribe(); + }; }); } public async fetchDashboards() { const scopeNames = getScopeNamesFromSelectedScopes(this.state.selector?.resolve().state.scopes ?? []); - if (isEqual(scopeNames, this.state.forScopeNames)) { - return; - } + this.dashboardsFetchingSub?.unsubscribe(); + + this.setState({ forScopeNames: scopeNames }); if (scopeNames.length === 0) { return this.setState({ @@ -95,18 +108,26 @@ export class ScopesDashboardsScene extends SceneObjectBase { + this.setState({ isLoading: false }); + }) + ) + .subscribe((dashboards) => { + const folders = groupDashboards(dashboards); + const filteredFolders = filterFolders(folders, this.state.searchQuery); - this.setState({ - dashboards, - folders, - filteredFolders, - forScopeNames: scopeNames, - isLoading: false, - scopesSelected: scopeNames.length > 0, - }); + this.setState({ + dashboards, + folders, + filteredFolders, + isLoading: false, + scopesSelected: scopeNames.length > 0, + }); + + this.dashboardsFetchingSub?.unsubscribe(); + }); } public changeSearchQuery(searchQuery: string) { @@ -148,14 +169,19 @@ export class ScopesDashboardsScene extends SceneObjectBase) { - const { dashboards, filteredFolders, isLoading, isPanelOpened, isEnabled, searchQuery, scopesSelected } = + const { dashboards, filteredFolders, isLoading, isPanelOpened, isEnabled, isReadOnly, searchQuery, scopesSelected } = model.useState(); const styles = useStyles2(getStyles); - if (!isEnabled || !isPanelOpened) { + if (!isEnabled || !isPanelOpened || isReadOnly) { return null; } diff --git a/public/app/features/scopes/internal/ScopesSelectorScene.tsx b/public/app/features/scopes/internal/ScopesSelectorScene.tsx index 1ca1237b545..e02ef57e17b 100644 --- a/public/app/features/scopes/internal/ScopesSelectorScene.tsx +++ b/public/app/features/scopes/internal/ScopesSelectorScene.tsx @@ -22,7 +22,12 @@ import { ScopesInput } from './ScopesInput'; import { ScopesTree } from './ScopesTree'; import { fetchNodes, fetchScope, fetchSelectedScopes } from './api'; import { NodeReason, NodesMap, SelectedScope, TreeScope } from './types'; -import { getBasicScope, getScopeNamesFromSelectedScopes, getTreeScopesFromSelectedScopes } from './utils'; +import { + getBasicScope, + getScopeNamesFromSelectedScopes, + getScopesAndTreeScopesWithPaths, + getTreeScopesFromSelectedScopes, +} from './utils'; export interface ScopesSelectorSceneState extends SceneObjectState { dashboards: SceneObjectRef | null; @@ -126,7 +131,14 @@ export class ScopesSelectorScene extends SceneObjectBase { - const persistedNodes = this.state.treeScopes + const [scopes, treeScopes] = getScopesAndTreeScopesWithPaths( + this.state.scopes, + this.state.treeScopes, + path, + childNodes + ); + + const persistedNodes = treeScopes .map(({ path }) => path[path.length - 1]) .filter((nodeName) => nodeName in currentNode.nodes && !(nodeName in childNodes)) .reduce((acc, nodeName) => { @@ -140,7 +152,7 @@ export class ScopesSelectorScene extends SceneObjectBase scope.metadata.name); } +// helper func to get the selected/tree scopes together with their paths +// needed to maintain selected scopes in tree for example when navigating +// between categories or when loading scopes from URL to find the scope's path +export function getScopesAndTreeScopesWithPaths( + selectedScopes: SelectedScope[], + treeScopes: TreeScope[], + path: string[], + childNodes: NodesMap +): [SelectedScope[], TreeScope[]] { + const childNodesArr = Object.values(childNodes); + + // Get all scopes without paths + // We use tree scopes as the list is always up to date as opposed to selected scopes which can be outdated + const scopeNamesWithoutPaths = treeScopes.filter(({ path }) => path.length === 0).map(({ scopeName }) => scopeName); + + // We search for the path of each scope name without a path + const scopeNamesWithPaths = scopeNamesWithoutPaths.reduce>((acc, scopeName) => { + const possibleParent = childNodesArr.find((childNode) => childNode.isSelectable && childNode.linkId === scopeName); + + if (possibleParent) { + acc[scopeName] = [...path, possibleParent.name]; + } + + return acc; + }, {}); + + // Update the paths of the selected scopes based on what we found + const newSelectedScopes = selectedScopes.map((selectedScope) => { + if (selectedScope.path.length > 0) { + return selectedScope; + } + + return { + ...selectedScope, + path: scopeNamesWithPaths[selectedScope.scope.metadata.name] ?? [], + }; + }); + + // Update the paths of the tree scopes based on what we found + const newTreeScopes = treeScopes.map((treeScope) => { + if (treeScope.path.length > 0) { + return treeScope; + } + + return { + ...treeScope, + path: scopeNamesWithPaths[treeScope.scopeName] ?? [], + }; + }); + + return [newSelectedScopes, newTreeScopes]; +} + export function groupDashboards(dashboards: ScopeDashboardBinding[]): SuggestedDashboardsFoldersMap { return dashboards.reduce( (acc, dashboard) => { diff --git a/public/app/features/scopes/tests/dashboardsList.test.ts b/public/app/features/scopes/tests/dashboardsList.test.ts index e5215a89f86..3b5f2fe1885 100644 --- a/public/app/features/scopes/tests/dashboardsList.test.ts +++ b/public/app/features/scopes/tests/dashboardsList.test.ts @@ -12,7 +12,9 @@ import { expectDashboardInDocument, expectDashboardLength, expectDashboardNotInDocument, + expectDashboardsClosed, expectDashboardSearchValue, + expectDashboardsOpen, expectDashboardsSearch, expectNoDashboardsForFilter, expectNoDashboardsForScope, @@ -45,9 +47,20 @@ describe('Dashboards list', () => { await resetScenes(); }); - it('Does not fetch dashboards list when the list is not expanded', async () => { + it('Opens container and fetches dashboards list when a scope is selected', async () => { + expectDashboardsClosed(); await updateScopes(['mimir']); - expect(fetchDashboardsSpy).not.toHaveBeenCalled(); + expectDashboardsOpen(); + expect(fetchDashboardsSpy).toHaveBeenCalled(); + }); + + it('Closes container when no scopes are selected', async () => { + await updateScopes(['mimir']); + expectDashboardsOpen(); + await updateScopes(['mimir', 'loki']); + expectDashboardsOpen(); + await updateScopes([]); + expectDashboardsClosed(); }); it('Fetches dashboards list when the list is expanded', async () => { diff --git a/public/app/features/scopes/tests/tree.test.ts b/public/app/features/scopes/tests/tree.test.ts index 2a456d73f6b..fab6a707baa 100644 --- a/public/app/features/scopes/tests/tree.test.ts +++ b/public/app/features/scopes/tests/tree.test.ts @@ -36,6 +36,8 @@ import { expectResultCloudOpsSelected, expectScopesHeadline, expectScopesSelectorValue, + expectSelectedScopePath, + expectTreeScopePath, } from './utils/assertions'; import { fetchNodesSpy, fetchScopeSpy, getDatasource, getInstanceSettings, getMock } from './utils/mocks'; import { renderDashboard, resetScenes } from './utils/render'; @@ -244,4 +246,28 @@ describe('Tree', () => { expect(fetchNodesSpy).toHaveBeenCalledTimes(3); expectScopesHeadline('No results found for your query'); }); + + it('Updates the paths for scopes without paths on nodes fetching', async () => { + const selectedScopeName = 'grafana'; + const unselectedScopeName = 'mimir'; + const selectedScopeNameFromOtherGroup = 'dev'; + + await updateScopes([selectedScopeName, selectedScopeNameFromOtherGroup]); + expectSelectedScopePath(selectedScopeName, []); + expectTreeScopePath(selectedScopeName, []); + expectSelectedScopePath(unselectedScopeName, undefined); + expectTreeScopePath(unselectedScopeName, undefined); + expectSelectedScopePath(selectedScopeNameFromOtherGroup, []); + expectTreeScopePath(selectedScopeNameFromOtherGroup, []); + + await openSelector(); + await expandResultApplications(); + const expectedPath = ['', 'applications', 'applications-grafana']; + expectSelectedScopePath(selectedScopeName, expectedPath); + expectTreeScopePath(selectedScopeName, expectedPath); + expectSelectedScopePath(unselectedScopeName, undefined); + expectTreeScopePath(unselectedScopeName, undefined); + expectSelectedScopePath(selectedScopeNameFromOtherGroup, []); + expectTreeScopePath(selectedScopeNameFromOtherGroup, []); + }); }); diff --git a/public/app/features/scopes/tests/utils/assertions.ts b/public/app/features/scopes/tests/utils/assertions.ts index 7ee5d4d1c58..cfd54c4ad78 100644 --- a/public/app/features/scopes/tests/utils/assertions.ts +++ b/public/app/features/scopes/tests/utils/assertions.ts @@ -1,6 +1,7 @@ import { getMock, locationReloadSpy } from './mocks'; import { getDashboard, + getDashboardsContainer, getDashboardsExpand, getDashboardsSearch, getNotFoundForFilter, @@ -12,8 +13,10 @@ import { getResultApplicationsMimirSelect, getResultCloudDevRadio, getResultCloudOpsRadio, + getSelectedScope, getSelectorInput, getTreeHeadline, + getTreeScope, queryAllDashboard, queryDashboard, queryDashboardFolderExpand, @@ -60,6 +63,7 @@ export const expectResultCloudOpsNotSelected = () => expectRadioNotChecked(getRe export const expectDashboardsDisabled = () => expectDisabled(getDashboardsExpand); export const expectDashboardsClosed = () => expectNotInDocument(queryDashboardsContainer); +export const expectDashboardsOpen = () => expectInDocument(getDashboardsContainer); export const expectNoDashboardsSearch = () => expectNotInDocument(queryDashboardsSearch); export const expectDashboardsSearch = () => expectInDocument(getDashboardsSearch); export const expectNoDashboardsNoScopes = () => expectInDocument(getNotFoundNoScopes); @@ -80,3 +84,8 @@ export const expectOldDashboardDTO = (scopes?: string[]) => expect(getMock).toHaveBeenCalledWith('/api/dashboards/uid/1', scopes ? { scopes } : undefined); export const expectNewDashboardDTO = () => expect(getMock).toHaveBeenCalledWith('/apis/dashboard.grafana.app/v0alpha1/namespaces/default/dashboards/1/dto'); + +export const expectSelectedScopePath = (name: string, path: string[] | undefined) => + expect(getSelectedScope(name)?.path).toEqual(path); +export const expectTreeScopePath = (name: string, path: string[] | undefined) => + expect(getTreeScope(name)?.path).toEqual(path); diff --git a/public/app/features/scopes/tests/utils/render.tsx b/public/app/features/scopes/tests/utils/render.tsx index 538ed293770..71d60ff6492 100644 --- a/public/app/features/scopes/tests/utils/render.tsx +++ b/public/app/features/scopes/tests/utils/render.tsx @@ -12,7 +12,6 @@ import { DashboardDataDTO, DashboardDTO, DashboardMeta } from 'app/types'; import { initializeScopes, scopesDashboardsScene, scopesSelectorScene } from '../../instance'; import { getInitialDashboardsState } from '../../internal/ScopesDashboardsScene'; import { initialSelectorState } from '../../internal/ScopesSelectorScene'; -import { DASHBOARDS_OPENED_KEY } from '../../internal/const'; import { clearMocks } from './actions'; @@ -160,7 +159,6 @@ export async function resetScenes() { await jest.runOnlyPendingTimersAsync(); jest.useRealTimers(); scopesSelectorScene?.setState(initialSelectorState); - localStorage.removeItem(DASHBOARDS_OPENED_KEY); scopesDashboardsScene?.setState(getInitialDashboardsState()); cleanup(); } diff --git a/public/app/features/scopes/tests/utils/selectors.ts b/public/app/features/scopes/tests/utils/selectors.ts index 655fe257420..b3174c0c6e3 100644 --- a/public/app/features/scopes/tests/utils/selectors.ts +++ b/public/app/features/scopes/tests/utils/selectors.ts @@ -1,5 +1,7 @@ import { screen } from '@testing-library/react'; +import { scopesSelectorScene } from '../../instance'; + const selectors = { tree: { search: 'scopes-tree-search', @@ -36,6 +38,7 @@ export const getSelectorApply = () => screen.getByTestId(selectors.selector.appl export const getSelectorCancel = () => screen.getByTestId(selectors.selector.cancel); export const getDashboardsExpand = () => screen.getByTestId(selectors.dashboards.expand); +export const getDashboardsContainer = () => screen.getByTestId(selectors.dashboards.container); export const queryDashboardsContainer = () => screen.queryByTestId(selectors.dashboards.container); export const queryDashboardsSearch = () => screen.queryByTestId(selectors.dashboards.search); export const getDashboardsSearch = () => screen.getByTestId(selectors.dashboards.search); @@ -82,3 +85,9 @@ export const getResultCloudDevRadio = () => screen.getByTestId(selectors.tree.radio('cloud-dev', 'result')); export const getResultCloudOpsRadio = () => screen.getByTestId(selectors.tree.radio('cloud-ops', 'result')); + +export const getListOfSelectedScopes = () => scopesSelectorScene?.state.scopes; +export const getListOfTreeScopes = () => scopesSelectorScene?.state.treeScopes; +export const getSelectedScope = (name: string) => + getListOfSelectedScopes()?.find((selectedScope) => selectedScope.scope.metadata.name === name); +export const getTreeScope = (name: string) => getListOfTreeScopes()?.find((treeScope) => treeScope.scopeName === name); diff --git a/public/app/features/scopes/utils.ts b/public/app/features/scopes/utils.ts index 85d01626fa8..29ddd616382 100644 --- a/public/app/features/scopes/utils.ts +++ b/public/app/features/scopes/utils.ts @@ -25,12 +25,12 @@ export function disableScopes() { export function exitScopesReadOnly() { scopesSelectorScene?.exitReadOnly(); - scopesDashboardsScene?.enable(); + scopesDashboardsScene?.exitReadOnly(); } export function enterScopesReadOnly() { scopesSelectorScene?.enterReadOnly(); - scopesDashboardsScene?.disable(); + scopesDashboardsScene?.enterReadOnly(); } export function getClosestScopesFacade(scene: SceneObject): ScopesFacade | null { diff --git a/public/app/features/search/service/searcher.ts b/public/app/features/search/service/searcher.ts index b7e8b885424..354411534c9 100644 --- a/public/app/features/search/service/searcher.ts +++ b/public/app/features/search/service/searcher.ts @@ -4,7 +4,6 @@ import { BlugeSearcher } from './bluge'; import { FrontendSearcher } from './frontend'; import { SQLSearcher } from './sql'; import { GrafanaSearcher } from './types'; -import { UnifiedSearcher } from './unified'; let searcher: GrafanaSearcher | undefined = undefined; @@ -14,11 +13,6 @@ export function getGrafanaSearcher(): GrafanaSearcher { const useBluge = config.featureToggles.panelTitleSearch; searcher = useBluge ? new BlugeSearcher(sqlSearcher) : sqlSearcher; - const useUnified = config.featureToggles.unifiedStorageSearch; - if (useUnified) { - searcher = new UnifiedSearcher(sqlSearcher); - } - if (useBluge && location.search.includes('do-frontend-query')) { searcher = new FrontendSearcher(searcher); } diff --git a/public/app/features/search/service/unified.ts b/public/app/features/search/service/unified.ts deleted file mode 100644 index 882b3a0a5bc..00000000000 --- a/public/app/features/search/service/unified.ts +++ /dev/null @@ -1,265 +0,0 @@ -// TODO: fix - copied from bluge.ts -import { - DataFrame, - DataFrameJSON, - DataFrameView, - getDisplayProcessor, - SelectableValue, - toDataFrame, -} from '@grafana/data'; -import { config, getBackendSrv } from '@grafana/runtime'; -import { TermCount } from 'app/core/components/TagFilter/TagFilter'; - -import { replaceCurrentFolderQuery } from './utils'; - -import { DashboardQueryResult, GrafanaSearcher, QueryResponse, SearchQuery } from '.'; - -// The backend returns an empty frame with a special name to indicate that the indexing engine is being rebuilt, -// and that it can not serve any search requests. We are temporarily using the old SQL Search API as a fallback when that happens. -const loadingFrameName = 'Loading'; - -const searchURI = 'api/unified-search'; - -type SearchAPIResponse = { - frames: DataFrameJSON[]; -}; - -const folderViewSort = 'name_sort'; - -export class UnifiedSearcher implements GrafanaSearcher { - constructor(private fallbackSearcher: GrafanaSearcher) {} - - async search(query: SearchQuery): Promise { - if (query.facet?.length) { - throw new Error('facets not supported!'); - } - return this.doSearchQuery(query); - } - - // TODO: fix - copied from bluge.ts - async starred(query: SearchQuery): Promise { - if (query.facet?.length) { - throw new Error('facets not supported!'); - } - // get the starred dashboards - const starsUIDS = await getBackendSrv().get('api/user/stars'); - if (starsUIDS?.length) { - return this.doSearchQuery({ - uid: starsUIDS, - query: query.query ?? '*', - }); - } - // Nothing is starred - return { - view: new DataFrameView({ length: 0, fields: [] }), - totalRows: 0, - loadMoreItems: async (startIndex: number, stopIndex: number): Promise => { - return; - }, - isItemLoaded: (index: number): boolean => { - return true; - }, - }; - } - - // TODO: fix - copied from bluge.ts - async tags(query: SearchQuery): Promise { - const req = { - ...query, - query: query.query ?? '*', - sort: undefined, // no need to sort the initial query results (not used) - facet: [{ field: 'tag' }], - limit: 1, // 0 would be better, but is ignored by the backend - }; - - const resp = await getBackendSrv().post(searchURI, req); - const frames = resp.frames.map((f) => toDataFrame(f)); - - if (frames[0]?.name === loadingFrameName) { - return this.fallbackSearcher.tags(query); - } - - for (const frame of frames) { - if (frame.fields[0].name === 'tag') { - return getTermCountsFrom(frame); - } - } - - return []; - } - - // TODO: fix - copied from bluge.ts - getSortOptions(): Promise { - const opts: SelectableValue[] = [ - { value: folderViewSort, label: 'Alphabetically (A-Z)' }, - { value: '-name_sort', label: 'Alphabetically (Z-A)' }, - ]; - - if (config.licenseInfo.enabledFeatures.analytics) { - for (const sf of sortFields) { - opts.push({ value: `-${sf.name}`, label: `${sf.display} (most)` }); - opts.push({ value: `${sf.name}`, label: `${sf.display} (least)` }); - } - for (const sf of sortTimeFields) { - opts.push({ value: `-${sf.name}`, label: `${sf.display} (recent)` }); - opts.push({ value: `${sf.name}`, label: `${sf.display} (oldest)` }); - } - } - - return Promise.resolve(opts); - } - - // TODO: update - copied from bluge.ts - async doSearchQuery(query: SearchQuery): Promise { - query = await replaceCurrentFolderQuery(query); - const req = { - ...query, - query: query.query ?? '*', - limit: query.limit ?? firstPageSize, - }; - - const rsp = await getBackendSrv().post(searchURI, req); - const frames = rsp.frames.map((f) => toDataFrame(f)); - - const first = frames.length ? toDataFrame(frames[0]) : { fields: [], length: 0 }; - - if (first.name === loadingFrameName) { - return this.fallbackSearcher.search(query); - } - - for (const field of first.fields) { - field.display = getDisplayProcessor({ field, theme: config.theme2 }); - } - - // Make sure the object exists - if (!first.meta?.custom) { - first.meta = { - ...first.meta, - custom: { - count: first.length, - max_score: 1, - }, - }; - } - - const meta = first.meta.custom || {}; - if (!meta.locationInfo) { - meta.locationInfo = {}; // always set it so we can append - } - - // Set the field name to a better display name - if (meta.sortBy?.length) { - const field = first.fields.find((f) => f.name === meta.sortBy); - if (field) { - const name = getSortFieldDisplayName(field.name); - meta.sortBy = name; - field.name = name; // make it look nicer - } - } - - let loadMax = 0; - let pending: Promise | undefined = undefined; - const getNextPage = async () => { - while (loadMax > view.dataFrame.length) { - const from = view.dataFrame.length; - if (from >= meta.count) { - return; - } - const resp = await getBackendSrv().post(searchURI, { - ...(req ?? {}), - from, - limit: nextPageSizes, - }); - const frame = toDataFrame(resp.frames[0]); - - if (!frame) { - console.log('no results', frame); - return; - } - if (frame.fields.length !== view.dataFrame.fields.length) { - console.log('invalid shape', frame, view.dataFrame); - return; - } - - // Append the raw values to the same array buffer - const length = frame.length + view.dataFrame.length; - for (let i = 0; i < frame.fields.length; i++) { - const values = view.dataFrame.fields[i].values; - values.push(...frame.fields[i].values); - } - view.dataFrame.length = length; - - // Add all the location lookup info - const submeta = frame.meta?.custom; - if (submeta?.locationInfo && meta) { - for (const [key, value] of Object.entries(submeta.locationInfo)) { - meta.locationInfo[key] = value; - } - } - } - pending = undefined; - }; - - const view = new DataFrameView(first); - return { - totalRows: meta.count ?? first.length, - view, - loadMoreItems: async (startIndex: number, stopIndex: number): Promise => { - loadMax = Math.max(loadMax, stopIndex); - if (!pending) { - pending = getNextPage(); - } - return pending; - }, - isItemLoaded: (index: number): boolean => { - return index < view.dataFrame.length; - }, - }; - } - - getFolderViewSort(): string { - return 'name_sort'; - } -} - -const firstPageSize = 50; -const nextPageSizes = 100; - -function getTermCountsFrom(frame: DataFrame): TermCount[] { - const keys = frame.fields[0].values; - const vals = frame.fields[1].values; - const counts: TermCount[] = []; - for (let i = 0; i < frame.length; i++) { - counts.push({ term: keys[i], count: vals[i] }); - } - return counts; -} - -// Enterprise only sort field values for dashboards -const sortFields = [ - { name: 'views_total', display: 'Views total' }, - { name: 'views_last_30_days', display: 'Views 30 days' }, - { name: 'errors_total', display: 'Errors total' }, - { name: 'errors_last_30_days', display: 'Errors 30 days' }, -]; - -// Enterprise only time sort field values for dashboards -const sortTimeFields = [ - { name: 'created_at', display: 'Created time' }, - { name: 'updated_at', display: 'Updated time' }, -]; - -/** Given the internal field name, this gives a reasonable display name for the table colum header */ -function getSortFieldDisplayName(name: string) { - for (const sf of sortFields) { - if (sf.name === name) { - return sf.display; - } - } - for (const sf of sortTimeFields) { - if (sf.name === name) { - return sf.display; - } - } - return name; -} diff --git a/public/app/features/trails/AutomaticMetricQueries/graph-builders/percentiles.ts b/public/app/features/trails/AutomaticMetricQueries/graph-builders/percentiles.ts index 9f40180d68d..36578e56ffb 100644 --- a/public/app/features/trails/AutomaticMetricQueries/graph-builders/percentiles.ts +++ b/public/app/features/trails/AutomaticMetricQueries/graph-builders/percentiles.ts @@ -1,10 +1,14 @@ import { PanelBuilders } from '@grafana/scenes'; +import { SortOrder } from '@grafana/schema'; +import { TooltipDisplayMode } from '@grafana/ui'; import { CommonVizParams } from './types'; export function percentilesGraphBuilder({ title, unit }: CommonVizParams) { - return PanelBuilders.timeseries() // + return PanelBuilders.timeseries() .setTitle(title) .setUnit(unit) - .setCustomFieldConfig('fillOpacity', 9); + .setCustomFieldConfig('fillOpacity', 9) + .setOption('tooltip', { mode: TooltipDisplayMode.Multi, sort: SortOrder.Descending }) + .setOption('legend', { showLegend: false }); } diff --git a/public/app/features/trails/AutomaticMetricQueries/graph-builders/simple.ts b/public/app/features/trails/AutomaticMetricQueries/graph-builders/simple.ts index 5eab7d70bfd..d04766f23e4 100644 --- a/public/app/features/trails/AutomaticMetricQueries/graph-builders/simple.ts +++ b/public/app/features/trails/AutomaticMetricQueries/graph-builders/simple.ts @@ -1,4 +1,6 @@ import { PanelBuilders } from '@grafana/scenes'; +import { SortOrder } from '@grafana/schema'; +import { TooltipDisplayMode } from '@grafana/ui'; import { CommonVizParams } from './types'; @@ -7,5 +9,6 @@ export function simpleGraphBuilder({ title, unit }: CommonVizParams) { .setTitle(title) .setUnit(unit) .setOption('legend', { showLegend: false }) + .setOption('tooltip', { mode: TooltipDisplayMode.Multi, sort: SortOrder.Descending }) .setCustomFieldConfig('fillOpacity', 9); } diff --git a/public/app/features/trails/Breakdown/LabelBreakdownScene.tsx b/public/app/features/trails/Breakdown/LabelBreakdownScene.tsx index 26b04788dda..b908c25f801 100644 --- a/public/app/features/trails/Breakdown/LabelBreakdownScene.tsx +++ b/public/app/features/trails/Breakdown/LabelBreakdownScene.tsx @@ -21,7 +21,7 @@ import { VariableDependencyConfig, VizPanel, } from '@grafana/scenes'; -import { DataQuery } from '@grafana/schema'; +import { DataQuery, SortOrder, TooltipDisplayMode } from '@grafana/schema'; import { Button, Field, LoadingPlaceholder, useStyles2 } from '@grafana/ui'; import { Trans } from 'app/core/internationalization'; @@ -39,7 +39,6 @@ import { AddToFiltersGraphAction } from './AddToFiltersGraphAction'; import { BreakdownSearchReset, BreakdownSearchScene } from './BreakdownSearchScene'; import { ByFrameRepeater } from './ByFrameRepeater'; import { LayoutSwitcher } from './LayoutSwitcher'; -import { breakdownPanelOptions } from './panelConfigs'; import { BreakdownLayoutChangeCallback, BreakdownLayoutType } from './types'; import { getLabelOptions } from './utils'; import { BreakdownAxisChangeEvent, yAxisSyncBehavior } from './yAxisSyncBehavior'; @@ -300,6 +299,8 @@ export function buildAllLayout( const unit = queryDef.unit; const vizPanel = PanelBuilders.timeseries() + .setOption('tooltip', { mode: TooltipDisplayMode.Multi, sort: SortOrder.Descending }) + .setOption('legend', { showLegend: false }) .setTitle(option.label!) .setData( new SceneQueryRunner({ @@ -319,10 +320,6 @@ export function buildAllLayout( .setBehaviors([fixLegendForUnspecifiedLabelValueBehavior]) .build(); - vizPanel.addActivationHandler(() => { - vizPanel.onOptionsChange(breakdownPanelOptions); - }); - children.push( new SceneCSSGridItem({ $behaviors: [yAxisSyncBehavior], @@ -382,10 +379,6 @@ function buildNormalLayout( isHidden, }); - vizPanel.addActivationHandler(() => { - vizPanel.onOptionsChange(breakdownPanelOptions); - }); - return item; } @@ -409,7 +402,11 @@ function buildNormalLayout( children: [ new SceneFlexItem({ minHeight: 300, - body: PanelBuilders.timeseries().setTitle('$metric').build(), + body: PanelBuilders.timeseries() + .setOption('tooltip', { mode: TooltipDisplayMode.Multi, sort: SortOrder.Descending }) + .setOption('legend', { showLegend: false }) + .setTitle('$metric') + .build(), }), ], }), diff --git a/public/app/features/trails/Breakdown/panelConfigs.ts b/public/app/features/trails/Breakdown/panelConfigs.ts deleted file mode 100644 index 23d796fdf8e..00000000000 --- a/public/app/features/trails/Breakdown/panelConfigs.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { PanelOptionsBuilders } from '@grafana/scenes'; -import { SortOrder } from '@grafana/schema/dist/esm/index'; -import { TooltipDisplayMode } from '@grafana/ui'; - -export const breakdownPanelOptions = PanelOptionsBuilders.timeseries() - .setOption('tooltip', { mode: TooltipDisplayMode.Multi, sort: SortOrder.Descending }) - .setOption('legend', { showLegend: false }) - .build(); diff --git a/public/app/features/trails/MetricGraphScene.tsx b/public/app/features/trails/MetricGraphScene.tsx index 5e439480f71..69003e9edc5 100644 --- a/public/app/features/trails/MetricGraphScene.tsx +++ b/public/app/features/trails/MetricGraphScene.tsx @@ -62,6 +62,8 @@ function getStyles(theme: GrafanaTheme2, chromeHeaderHeight: number) { flexDirection: 'row', background: theme.isLight ? theme.colors.background.primary : theme.colors.background.canvas, position: 'sticky', + paddingTop: theme.spacing(1), + marginTop: `-${theme.spacing(1)}`, top: `${chromeHeaderHeight + 70}px`, zIndex: 10, }), diff --git a/public/app/plugins/datasource/azuremonitor/package.json b/public/app/plugins/datasource/azuremonitor/package.json index 5b8cc33a8fc..b7e2c1fc43a 100644 --- a/public/app/plugins/datasource/azuremonitor/package.json +++ b/public/app/plugins/datasource/azuremonitor/package.json @@ -6,7 +6,7 @@ "dependencies": { "@emotion/css": "11.13.4", "@grafana/data": "11.3.0-pre", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/runtime": "11.3.0-pre", "@grafana/schema": "11.3.0-pre", "@grafana/ui": "11.3.0-pre", diff --git a/public/app/plugins/datasource/cloud-monitoring/package.json b/public/app/plugins/datasource/cloud-monitoring/package.json index daba101b281..22c4d50f4da 100644 --- a/public/app/plugins/datasource/cloud-monitoring/package.json +++ b/public/app/plugins/datasource/cloud-monitoring/package.json @@ -6,7 +6,7 @@ "dependencies": { "@emotion/css": "11.13.4", "@grafana/data": "11.3.0-pre", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/google-sdk": "0.1.2", "@grafana/runtime": "11.3.0-pre", "@grafana/schema": "11.3.0-pre", diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json index a002e77c490..8c7a04589cc 100644 --- a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json +++ b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json @@ -6,7 +6,7 @@ "dependencies": { "@emotion/css": "11.13.4", "@grafana/data": "11.3.0-pre", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/runtime": "11.3.0-pre", "@grafana/sql": "11.3.0-pre", "@grafana/ui": "11.3.0-pre", diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/package.json b/public/app/plugins/datasource/grafana-testdata-datasource/package.json index cb4a0b23e73..2d3a0165f48 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/package.json +++ b/public/app/plugins/datasource/grafana-testdata-datasource/package.json @@ -6,7 +6,7 @@ "dependencies": { "@emotion/css": "11.13.4", "@grafana/data": "11.3.0-pre", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/runtime": "11.3.0-pre", "@grafana/schema": "11.3.0-pre", "@grafana/ui": "11.3.0-pre", diff --git a/public/app/plugins/datasource/jaeger/package.json b/public/app/plugins/datasource/jaeger/package.json index 66cd27b8eac..9811041480a 100644 --- a/public/app/plugins/datasource/jaeger/package.json +++ b/public/app/plugins/datasource/jaeger/package.json @@ -7,7 +7,7 @@ "@emotion/css": "11.13.4", "@grafana/data": "workspace:*", "@grafana/e2e-selectors": "workspace:*", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/o11y-ds-frontend": "workspace:*", "@grafana/runtime": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/public/app/plugins/datasource/mssql/package.json b/public/app/plugins/datasource/mssql/package.json index 51cbcb5dff3..1f0a2e0b5e6 100644 --- a/public/app/plugins/datasource/mssql/package.json +++ b/public/app/plugins/datasource/mssql/package.json @@ -6,7 +6,7 @@ "dependencies": { "@emotion/css": "11.13.4", "@grafana/data": "11.3.0-pre", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/runtime": "11.3.0-pre", "@grafana/sql": "11.3.0-pre", "@grafana/ui": "11.3.0-pre", diff --git a/public/app/plugins/datasource/mysql/package.json b/public/app/plugins/datasource/mysql/package.json index cbb68d22ebe..1531f4f935d 100644 --- a/public/app/plugins/datasource/mysql/package.json +++ b/public/app/plugins/datasource/mysql/package.json @@ -6,7 +6,7 @@ "dependencies": { "@emotion/css": "11.13.4", "@grafana/data": "11.3.0-pre", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/runtime": "11.3.0-pre", "@grafana/sql": "11.3.0-pre", "@grafana/ui": "11.3.0-pre", diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json index 53e42363454..63d2b5c785f 100644 --- a/public/app/plugins/datasource/tempo/package.json +++ b/public/app/plugins/datasource/tempo/package.json @@ -7,7 +7,7 @@ "@emotion/css": "11.13.4", "@grafana/data": "workspace:*", "@grafana/e2e-selectors": "workspace:*", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/lezer-logql": "0.2.6", "@grafana/lezer-traceql": "0.0.19", "@grafana/monaco-logql": "^0.0.7", diff --git a/public/app/plugins/datasource/tempo/streaming.ts b/public/app/plugins/datasource/tempo/streaming.ts index 71c336edf2f..333faca7388 100644 --- a/public/app/plugins/datasource/tempo/streaming.ts +++ b/public/app/plugins/datasource/tempo/streaming.ts @@ -84,9 +84,12 @@ export function doTempoChannelStream( throw new Error(error); } + // The order of the frames is important. The metrics frame should always be the last frame. + // This is because the metrics frame is used to display the progress of the streaming query + // and we would like to display the results first. frames = [ - metricsDataFrame(metrics, frameState, elapsedTime), ...formatTraceQLResponse(traces, instanceSettings, query.tableType), + metricsDataFrame(metrics, frameState, elapsedTime), ]; } return { diff --git a/public/app/plugins/datasource/zipkin/package.json b/public/app/plugins/datasource/zipkin/package.json index 5279c2da89b..0d91729726b 100644 --- a/public/app/plugins/datasource/zipkin/package.json +++ b/public/app/plugins/datasource/zipkin/package.json @@ -7,7 +7,7 @@ "@emotion/css": "11.13.4", "@grafana/data": "workspace:*", "@grafana/e2e-selectors": "workspace:*", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/o11y-ds-frontend": "workspace:*", "@grafana/runtime": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/public/app/plugins/panel/timeseries/TimezonesEditor.tsx b/public/app/plugins/panel/timeseries/TimezonesEditor.tsx index d62a538aebf..968c68727ac 100644 --- a/public/app/plugins/panel/timeseries/TimezonesEditor.tsx +++ b/public/app/plugins/panel/timeseries/TimezonesEditor.tsx @@ -34,36 +34,34 @@ export const TimezonesEditor = ({ value, onChange }: Props) => { }; return ( -
+
    {value.map((tz, idx) => ( -
    - - setTimezone(idx, v)} - includeInternal={true} - value={tz ?? InternalTimeZones.default} - /> - +
  • + setTimezone(idx, v)} + includeInternal={true} + value={tz ?? InternalTimeZones.default} + /> {idx === value.length - 1 ? ( ) : ( removeTimezone(idx)} tooltip="Remove timezone" /> )} -
  • + ))} -
+ ); }; const getStyles = (theme: GrafanaTheme2) => ({ - wrapper: css` - width: 100%; - display: flex; - flex-direction: rows; - align-items: center; - `, - first: css` - margin-right: 8px; - flex-grow: 2; - `, + list: css({ + listStyle: 'none', + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(0.5), + }), + listItem: css({ + display: 'flex', + gap: theme.spacing(1), + }), }); diff --git a/public/app/routes/routes.tsx b/public/app/routes/routes.tsx index 405c76ee35f..3355616189d 100644 --- a/public/app/routes/routes.tsx +++ b/public/app/routes/routes.tsx @@ -23,6 +23,7 @@ import { SafeDynamicImport } from '../core/components/DynamicImports/SafeDynamic import { RouteDescriptor } from '../core/navigation/types'; import { getPublicDashboardRoutes } from '../features/dashboard/routes'; +const isDevEnv = config.buildInfo.env === 'development'; export const extraRoutes: RouteDescriptor[] = []; export function getAppRoutes(): RouteDescriptor[] { @@ -198,6 +199,15 @@ export function getAppRoutes(): RouteDescriptor[] { path: '/admin/plugins', component: () => , }, + { + path: '/admin/extensions', + navId: 'extensions', + component: isDevEnv + ? SafeDynamicImport( + () => import(/* webpackChunkName: "PluginExtensionsLog" */ 'app/features/plugins/extensions/logs/LogViewer') + ) + : () => , + }, { path: '/admin/access', component: () => , @@ -434,9 +444,9 @@ export function getAppRoutes(): RouteDescriptor[] { () => import(/* webpackChunkName: "SnapshotListPage" */ 'app/features/manage-dashboards/SnapshotListPage') ), }, - config.featureToggles.dashboardRestoreUI && { + config.featureToggles.dashboardRestore && { path: '/dashboard/recently-deleted', - roles: () => ['Admin'], + roles: () => ['Admin', 'ServerAdmin'], component: SafeDynamicImport( () => import(/* webpackChunkName: "RecentlyDeletedPage" */ 'app/features/browse-dashboards/RecentlyDeletedPage') ), diff --git a/public/img/icons/unicons/api-endpoint.svg b/public/img/icons/unicons/api-endpoint.svg new file mode 100644 index 00000000000..1d300cffc63 --- /dev/null +++ b/public/img/icons/unicons/api-endpoint.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/img/icons/unicons/browser-alt.svg b/public/img/icons/unicons/browser-alt.svg new file mode 100644 index 00000000000..253275890d3 --- /dev/null +++ b/public/img/icons/unicons/browser-alt.svg @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/public/img/icons/unicons/k6-rounded.svg b/public/img/icons/unicons/k6-rounded.svg new file mode 100644 index 00000000000..101c269716e --- /dev/null +++ b/public/img/icons/unicons/k6-rounded.svg @@ -0,0 +1,11 @@ + + + + + diff --git a/public/img/icons/unicons/multi-step.svg b/public/img/icons/unicons/multi-step.svg new file mode 100644 index 00000000000..eea231cd406 --- /dev/null +++ b/public/img/icons/unicons/multi-step.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index a2be614eb02..ad79affc6b5 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -2218,7 +2218,9 @@ "datasource-names": "", "delete-query-button": "", "query-template-get-error": "", - "search": "" + "search": "", + "user-info-get-error": "", + "user-names": "" }, "query-operation": { "header": { diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 0823f93c9b3..2b6cff36111 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -831,8 +831,8 @@ "private": "Private", "public": "Public", "query-deleted": "Query deleted", - "query-template-add-error": "Error attempting to add this query to the library", - "query-template-added": "Query template successfully added to the library", + "query-template-add-error": "Error attempting to save this query to the library", + "query-template-added": "Query successfully saved to the library", "query-template-edit-error": "Error attempting to edit this query", "query-template-edited": "Query template successfully edited", "save": "Save" diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index 76a3bd0426b..9ab396e02e5 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -2218,7 +2218,9 @@ "datasource-names": "", "delete-query-button": "", "query-template-get-error": "", - "search": "" + "search": "", + "user-info-get-error": "", + "user-names": "" }, "query-operation": { "header": { diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 711c3eb893d..da3b6c03325 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -2218,7 +2218,9 @@ "datasource-names": "", "delete-query-button": "", "query-template-get-error": "", - "search": "" + "search": "", + "user-info-get-error": "", + "user-names": "" }, "query-operation": { "header": { diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index a2a87a13fd4..c5c78519a42 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -831,8 +831,8 @@ "private": "Přįväŧę", "public": "Pūþľįč", "query-deleted": "Qūęřy đęľęŧęđ", - "query-template-add-error": "Ēřřőř äŧŧęmpŧįʼnģ ŧő äđđ ŧĥįş qūęřy ŧő ŧĥę ľįþřäřy", - "query-template-added": "Qūęřy ŧęmpľäŧę şūččęşşƒūľľy äđđęđ ŧő ŧĥę ľįþřäřy", + "query-template-add-error": "Ēřřőř äŧŧęmpŧįʼnģ ŧő şävę ŧĥįş qūęřy ŧő ŧĥę ľįþřäřy", + "query-template-added": "Qūęřy şūččęşşƒūľľy şävęđ ŧő ŧĥę ľįþřäřy", "query-template-edit-error": "Ēřřőř äŧŧęmpŧįʼnģ ŧő ęđįŧ ŧĥįş qūęřy", "query-template-edited": "Qūęřy ŧęmpľäŧę şūččęşşƒūľľy ęđįŧęđ", "save": "Ŝävę" diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index 0c02ca59833..edb0a1a470e 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -2218,7 +2218,9 @@ "datasource-names": "", "delete-query-button": "", "query-template-get-error": "", - "search": "" + "search": "", + "user-info-get-error": "", + "user-names": "" }, "query-operation": { "header": { diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index b4c7740e3ab..20bc710c828 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -2208,7 +2208,9 @@ "datasource-names": "", "delete-query-button": "", "query-template-get-error": "", - "search": "" + "search": "", + "user-info-get-error": "", + "user-names": "" }, "query-operation": { "header": { diff --git a/public/test/helpers/alertingRuleEditor.tsx b/public/test/helpers/alertingRuleEditor.tsx index b40cdb66d64..e07a335fad9 100644 --- a/public/test/helpers/alertingRuleEditor.tsx +++ b/public/test/helpers/alertingRuleEditor.tsx @@ -1,4 +1,4 @@ -import { Route } from 'react-router-dom'; +import { Routes, Route } from 'react-router-dom-v5-compat'; import { render } from 'test/test-utils'; import { byRole, byTestId, byText } from 'testing-library-selector'; @@ -35,11 +35,17 @@ export const ui = { }; export function renderRuleEditor(identifier?: string, recording = false) { - return render(, { - historyOptions: { - initialEntries: [ - identifier ? `/alerting/${identifier}/edit` : `/alerting/new/${recording ? 'recording' : 'alerting'}`, - ], - }, - }); + return render( + + } /> + } /> + , + { + historyOptions: { + initialEntries: [ + identifier ? `/alerting/${identifier}/edit` : `/alerting/new/${recording ? 'recording' : 'alerting'}`, + ], + }, + } + ); } diff --git a/public/test/jest-setup.ts b/public/test/jest-setup.ts index f47f43ecb60..17579226432 100644 --- a/public/test/jest-setup.ts +++ b/public/test/jest-setup.ts @@ -117,3 +117,12 @@ global.ResizeObserver = class ResizeObserver { disconnect() {} unobserve() {} }; + +global.BroadcastChannel = class BroadcastChannel { + onmessage() {} + onmessageerror() {} + postMessage(data: unknown) {} + close() {} + addEventListener() {} + removeEventListener() {} +}; diff --git a/scripts/check-breaking-changes.sh b/scripts/check-breaking-changes.sh index 07ff141e6ba..d37a6c23669 100755 --- a/scripts/check-breaking-changes.sh +++ b/scripts/check-breaking-changes.sh @@ -55,7 +55,7 @@ while IFS=" " read -r -a package; do # (non-zero if any of the packages failed the checks) if [ "$STATUS" -gt 0 ]; then EXIT_CODE=1 - GITHUB_MESSAGE="${GITHUB_MESSAGE}**\\\`${PACKAGE_PATH}\\\`** has possible breaking changes ([more info](${GITHUB_JOB_LINK}#step:${GITHUB_STEP_NUMBER}:1))
" + GITHUB_MESSAGE="${GITHUB_MESSAGE}**\\\`${PACKAGE_PATH}\\\`** has possible breaking changes
" GITHUB_LEVITATE_MARKDOWN+="

${PACKAGE_PATH}

${CURRENT_REPORT}
" fi diff --git a/yarn.lock b/yarn.lock index 915142467a8..22f8b8cc7db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -92,16 +92,16 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/compat-data@npm:7.25.7" - checksum: 10/8fdc451e0ed9e22d1324d504b84d4452ba6f4a806b0f5c364996ee4c2a77293f79ecf4da03033acb625c90bac115c61617eb6c894c2b88486724bcbe3af1a6eb +"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.25.7, @babel/compat-data@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/compat-data@npm:7.25.8" + checksum: 10/269fcb0d89e02e36c8a11e0c1b960a6b4204e88f59f20c374d28f8e318f4cd5ded42dfedc4b54162065e6a10f71c0de651f5ed3f9b45d3a4b52240196df85726 languageName: node linkType: hard -"@babel/core@npm:7.25.7, @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.23.0, @babel/core@npm:^7.24.4": - version: 7.25.7 - resolution: "@babel/core@npm:7.25.7" +"@babel/core@npm:7.25.8, @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.23.0, @babel/core@npm:^7.24.4": + version: 7.25.8 + resolution: "@babel/core@npm:7.25.8" dependencies: "@ampproject/remapping": "npm:^2.2.0" "@babel/code-frame": "npm:^7.25.7" @@ -109,16 +109,16 @@ __metadata: "@babel/helper-compilation-targets": "npm:^7.25.7" "@babel/helper-module-transforms": "npm:^7.25.7" "@babel/helpers": "npm:^7.25.7" - "@babel/parser": "npm:^7.25.7" + "@babel/parser": "npm:^7.25.8" "@babel/template": "npm:^7.25.7" "@babel/traverse": "npm:^7.25.7" - "@babel/types": "npm:^7.25.7" + "@babel/types": "npm:^7.25.8" 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/f5fb7fb1e3ce357485cb33fe7984051a2d416472370b33144ae809df86a4663192b58cf0d828d40674d30f485790f3dd5aaf72eb659487673a4dc4be47cb3575 + checksum: 10/31eb1a8ca1a3cc0026060720eb290e68205d95c5c00fbd831e69ddc0810f5920b8eb2749db1889ac0a0312b6eddbf321d18a996a88858f3b75c9582bef9ec1e4 languageName: node linkType: hard @@ -371,14 +371,14 @@ __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.23.0, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/parser@npm:7.25.7" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.0, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.25.7, @babel/parser@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/parser@npm:7.25.8" dependencies: - "@babel/types": "npm:^7.25.7" + "@babel/types": "npm:^7.25.8" bin: parser: ./bin/babel-parser.js - checksum: 10/98eaa81bd378734a5f2790f02c7c076ecaba0839217445b4b84f45a7b391d640c34034253231a5bb2b2daf8204796f03584c3f94c10d46b004369bbb426a418f + checksum: 10/0396eb71e379903cedb43862f84ebb1bec809c41e82b4894d2e6e83b8e8bc636ba6eff45382e615baefdb2399ede76ca82247ecc3a9877ac16eb3140074a3276 languageName: node linkType: hard @@ -484,7 +484,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-class-properties@npm:^7.0.0, @babel/plugin-syntax-class-properties@npm:^7.12.13, @babel/plugin-syntax-class-properties@npm:^7.8.3": +"@babel/plugin-syntax-class-properties@npm:^7.0.0, @babel/plugin-syntax-class-properties@npm:^7.8.3": version: 7.12.13 resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" dependencies: @@ -495,17 +495,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-class-static-block@npm:^7.14.5": - version: 7.14.5 - resolution: "@babel/plugin-syntax-class-static-block@npm:7.14.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/3e80814b5b6d4fe17826093918680a351c2d34398a914ce6e55d8083d72a9bdde4fbaf6a2dcea0e23a03de26dc2917ae3efd603d27099e2b98380345703bf948 - languageName: node - linkType: hard - "@babel/plugin-syntax-dynamic-import@npm:^7.8.3": version: 7.8.3 resolution: "@babel/plugin-syntax-dynamic-import@npm:7.8.3" @@ -561,7 +550,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-import-meta@npm:^7.10.4, @babel/plugin-syntax-import-meta@npm:^7.8.3": +"@babel/plugin-syntax-import-meta@npm:^7.8.3": version: 7.10.4 resolution: "@babel/plugin-syntax-import-meta@npm:7.10.4" dependencies: @@ -594,7 +583,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-logical-assignment-operators@npm:^7.10.4, @babel/plugin-syntax-logical-assignment-operators@npm:^7.8.3": +"@babel/plugin-syntax-logical-assignment-operators@npm:^7.8.3": version: 7.10.4 resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4" dependencies: @@ -616,7 +605,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-numeric-separator@npm:^7.10.4, @babel/plugin-syntax-numeric-separator@npm:^7.8.3": +"@babel/plugin-syntax-numeric-separator@npm:^7.8.3": version: 7.10.4 resolution: "@babel/plugin-syntax-numeric-separator@npm:7.10.4" dependencies: @@ -660,18 +649,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-private-property-in-object@npm:^7.14.5": - version: 7.14.5 - resolution: "@babel/plugin-syntax-private-property-in-object@npm:7.14.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/b317174783e6e96029b743ccff2a67d63d38756876e7e5d0ba53a322e38d9ca452c13354a57de1ad476b4c066dbae699e0ca157441da611117a47af88985ecda - languageName: node - linkType: hard - -"@babel/plugin-syntax-top-level-await@npm:^7.14.5, @babel/plugin-syntax-top-level-await@npm:^7.8.3": +"@babel/plugin-syntax-top-level-await@npm:^7.8.3": version: 7.14.5 resolution: "@babel/plugin-syntax-top-level-await@npm:7.14.5" dependencies: @@ -716,17 +694,16 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-async-generator-functions@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/plugin-transform-async-generator-functions@npm:7.25.7" +"@babel/plugin-transform-async-generator-functions@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/plugin-transform-async-generator-functions@npm:7.25.8" dependencies: "@babel/helper-plugin-utils": "npm:^7.25.7" "@babel/helper-remap-async-to-generator": "npm:^7.25.7" - "@babel/plugin-syntax-async-generators": "npm:^7.8.4" "@babel/traverse": "npm:^7.25.7" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/f5e14796bcb04db7f045833d695e49bb1178162c74754f67f25fd6934ebbd0e57a59784d76a23b3f472bbd3e5a0c33d433ab60e7c6a5c7ca240b54d8ca231baa + checksum: 10/ab3f74664fc03af357e8450711de60ec77149be668059dbc0c0d616d85253117aec0e5ffb2eccda3449d0099d5fba5ef32f0e6e12a52af5f72fbca437372ece5 languageName: node linkType: hard @@ -777,16 +754,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-class-static-block@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/plugin-transform-class-static-block@npm:7.25.7" +"@babel/plugin-transform-class-static-block@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/plugin-transform-class-static-block@npm:7.25.8" dependencies: "@babel/helper-create-class-features-plugin": "npm:^7.25.7" "@babel/helper-plugin-utils": "npm:^7.25.7" - "@babel/plugin-syntax-class-static-block": "npm:^7.14.5" peerDependencies: "@babel/core": ^7.12.0 - checksum: 10/d6fa7132071860d4b6e58baa43c8efdd241d5b4ff3d31b0b6593390c7b39bf17ab549e427c08db550c84b0fb02eaad41fc96c2d299fbee4c0a6030315b0a5296 + checksum: 10/160d5f9d1dbe4dc12c2998227b51b1ccfe9f4d11b1031d0698f34403961d5b9bb995cc86acf1855102b9be365370c97d8cea243802b73c7ba7b2b18b2ac3aae9 languageName: node linkType: hard @@ -864,15 +840,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-dynamic-import@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/plugin-transform-dynamic-import@npm:7.25.7" +"@babel/plugin-transform-dynamic-import@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/plugin-transform-dynamic-import@npm:7.25.8" dependencies: "@babel/helper-plugin-utils": "npm:^7.25.7" - "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/a153f2a8b10a733f884a0ba0e680387d0ba643d40774885c26cceef90cc61b8e2f946a64e1764605189498a9a04e1d4da4e886c3e26a850e7c4e5ea5fb4b3f50 + checksum: 10/cf2c105143461876f418d21893ac8f7f2b0a3c3cefb4374c3cd6338a19d3a0deed3565049f7436b94452c6471622958ef9248c7bdfeb34d2917710ac74431203 languageName: node linkType: hard @@ -888,15 +863,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-export-namespace-from@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/plugin-transform-export-namespace-from@npm:7.25.7" +"@babel/plugin-transform-export-namespace-from@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/plugin-transform-export-namespace-from@npm:7.25.8" dependencies: "@babel/helper-plugin-utils": "npm:^7.25.7" - "@babel/plugin-syntax-export-namespace-from": "npm:^7.8.3" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/e3ec589906380d1daa3349c358f8a56c9adc1c5003b4069321172e1e8160f2a497e9ae112ad1ac68e26dce33eb19019932cf938ad411493441ad202db0e72c2b + checksum: 10/439aac4ca1c7dbb63f021142e7abcd746049bf0d44cc5d2eb469ae3b75d90e076a43ff77190b74d8139402b53eea625b08c68651d3ce1d0a0915f5643450b3de languageName: node linkType: hard @@ -937,15 +911,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-json-strings@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/plugin-transform-json-strings@npm:7.25.7" +"@babel/plugin-transform-json-strings@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/plugin-transform-json-strings@npm:7.25.8" dependencies: "@babel/helper-plugin-utils": "npm:^7.25.7" - "@babel/plugin-syntax-json-strings": "npm:^7.8.3" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/99e7b19d5b8f0df78eb2eaef2c1c8734e634b08910b47d5c2f61e43e75bd94a9f8e51c8baf2d2fa181bf0a7424c08b0aee29a95ae5f9e59f50e51e188dc943a1 + checksum: 10/adbc6a5a77b96db0f7e168c5fd2e56941df649808ce960f12447c1ba5d3893e9d458e7e14e3a5bd725ac5f3432ac1b3cf62b7413bbf7168a7c656dce51db711a languageName: node linkType: hard @@ -960,15 +933,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-logical-assignment-operators@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.25.7" +"@babel/plugin-transform-logical-assignment-operators@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.25.8" dependencies: "@babel/helper-plugin-utils": "npm:^7.25.7" - "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/28d00bae5637564243585e4fa044b9bc1da4b5c2ac2859e848e2db5f1c11c24dbc1ecc690755a00f593815a40d9cd99df2b31110ab68626840b22598ecf08d93 + checksum: 10/7af0e4ad63c1a59f24894b64330040966204963b75287752a2d56703c7924d3a883a3c2497e1f03c4b1792f8664e0650cf6687010dc5483444c077de1daae9f5 languageName: node linkType: hard @@ -1057,41 +1029,38 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.22.11, @babel/plugin-transform-nullish-coalescing-operator@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.25.7" +"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.22.11, @babel/plugin-transform-nullish-coalescing-operator@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.25.8" dependencies: "@babel/helper-plugin-utils": "npm:^7.25.7" - "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/af4d2d17eb0bdfa4a0414eb02a402f65226363ea3cc6d781028fd0051893918eeb3cf13aaceef8ef58ef35b36362dbcc25cd018e0c24b5b441226e086bf3b58f + checksum: 10/d742fedc1abf404d7f40065cdff9afc521236607f0d06c48d1e471f43d3a7471010d1651ba4758d80c73347a39dc278d86c43a9c814382ded4e9c7c519ace021 languageName: node linkType: hard -"@babel/plugin-transform-numeric-separator@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/plugin-transform-numeric-separator@npm:7.25.7" +"@babel/plugin-transform-numeric-separator@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/plugin-transform-numeric-separator@npm:7.25.8" dependencies: "@babel/helper-plugin-utils": "npm:^7.25.7" - "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/963ff47d27b7e61e0b346a29e2ff86ccf4fb779f7069b54c09e93376e6ed769523d81b9ef4ef60ae2d8fe35f3411a25d4b2e8d0a23850afa821f47f33c7b510c + checksum: 10/e27779a309dbc5fdba71d7eae0eac5506547632b0cbf8f0add8215797bbda4f4e61595750236fee3292600cc2d13892f133beccc52b2998534e0b10c668db857 languageName: node linkType: hard -"@babel/plugin-transform-object-rest-spread@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/plugin-transform-object-rest-spread@npm:7.25.7" +"@babel/plugin-transform-object-rest-spread@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/plugin-transform-object-rest-spread@npm:7.25.8" dependencies: "@babel/helper-compilation-targets": "npm:^7.25.7" "@babel/helper-plugin-utils": "npm:^7.25.7" - "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" "@babel/plugin-transform-parameters": "npm:^7.25.7" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/b831c56921cb4818956941fd6eb54ba75fa51e5e5c1dc00466d70c79fea2a73d166a6ff02e8a27d63e40238de49f966af2bd243a8b6d281f784672f1a285d47c + checksum: 10/38f0fab8321a0b1e44784b7371f8bd5601eb885a7e9d88d7904dedda33a72f500d84792758c47e1541336c1b7592b6d956a85c2fd8e2e294f34c0303cc73442c languageName: node linkType: hard @@ -1107,28 +1076,26 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-optional-catch-binding@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.25.7" +"@babel/plugin-transform-optional-catch-binding@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.25.8" dependencies: "@babel/helper-plugin-utils": "npm:^7.25.7" - "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/b195680e047bd2e0e36debfb75e5649f0fd8c88185e33c627c5418c68f9990cb4d96faccfbf47a2fdf9ff848a9e013ca077647616aa9dc1f78fa250ef1521437 + checksum: 10/9ecf32accc5b12b83ce2f6537c9eac87f2b0f89abfe91a8a8c87ea5ece05820988415271d0fdaf7f565e2c0c837afb24fc644779029b98b1401782d9c0d73642 languageName: node linkType: hard -"@babel/plugin-transform-optional-chaining@npm:^7.23.0, @babel/plugin-transform-optional-chaining@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/plugin-transform-optional-chaining@npm:7.25.7" +"@babel/plugin-transform-optional-chaining@npm:^7.23.0, @babel/plugin-transform-optional-chaining@npm:^7.25.7, @babel/plugin-transform-optional-chaining@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/plugin-transform-optional-chaining@npm:7.25.8" dependencies: "@babel/helper-plugin-utils": "npm:^7.25.7" "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.7" - "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/9c0a742eb3111f250b95065cd7d27b31c724d3f3c29b1de3c6655632172f69d5aa658d3ebff76d3b530b6792b6f4cf4845f4132ac8a1fea6e57ff19e3ec6f531 + checksum: 10/ffb5d81e6dbb28907d5346c8e12a1ed1ea0e30170fbe609d48d0466cdbc9d11b5774c8781682693f7cf7bd39da6111980e54813af96c6b3086dc769369c67d28 languageName: node linkType: hard @@ -1155,17 +1122,16 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-private-property-in-object@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/plugin-transform-private-property-in-object@npm:7.25.7" +"@babel/plugin-transform-private-property-in-object@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/plugin-transform-private-property-in-object@npm:7.25.8" dependencies: "@babel/helper-annotate-as-pure": "npm:^7.25.7" "@babel/helper-create-class-features-plugin": "npm:^7.25.7" "@babel/helper-plugin-utils": "npm:^7.25.7" - "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/c23786b9c123cceb0f69ab074377236825d76db090413db0b92d1a412b0e488e7ebde3e6155883ddbce6616114c54693485274a5d2b8a9a280b40781f80bd343 + checksum: 10/c612023879930c951e3a993104bbc3b78169aef6c38233758ee3358a7ab76954b41880bca67635df218dc6893aabad138f3783d508dc715419e62c8d1fad9088 languageName: node linkType: hard @@ -1379,11 +1345,11 @@ __metadata: languageName: node linkType: hard -"@babel/preset-env@npm:7.25.7, @babel/preset-env@npm:^7.24.4": - version: 7.25.7 - resolution: "@babel/preset-env@npm:7.25.7" +"@babel/preset-env@npm:7.25.8, @babel/preset-env@npm:^7.24.4": + version: 7.25.8 + resolution: "@babel/preset-env@npm:7.25.8" dependencies: - "@babel/compat-data": "npm:^7.25.7" + "@babel/compat-data": "npm:^7.25.8" "@babel/helper-compilation-targets": "npm:^7.25.7" "@babel/helper-plugin-utils": "npm:^7.25.7" "@babel/helper-validator-option": "npm:^7.25.7" @@ -1393,45 +1359,30 @@ __metadata: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "npm:^7.25.7" "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "npm:^7.25.7" "@babel/plugin-proposal-private-property-in-object": "npm:7.21.0-placeholder-for-preset-env.2" - "@babel/plugin-syntax-async-generators": "npm:^7.8.4" - "@babel/plugin-syntax-class-properties": "npm:^7.12.13" - "@babel/plugin-syntax-class-static-block": "npm:^7.14.5" - "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" - "@babel/plugin-syntax-export-namespace-from": "npm:^7.8.3" "@babel/plugin-syntax-import-assertions": "npm:^7.25.7" "@babel/plugin-syntax-import-attributes": "npm:^7.25.7" - "@babel/plugin-syntax-import-meta": "npm:^7.10.4" - "@babel/plugin-syntax-json-strings": "npm:^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" - "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4" - "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" - "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3" - "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" - "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5" - "@babel/plugin-syntax-top-level-await": "npm:^7.14.5" "@babel/plugin-syntax-unicode-sets-regex": "npm:^7.18.6" "@babel/plugin-transform-arrow-functions": "npm:^7.25.7" - "@babel/plugin-transform-async-generator-functions": "npm:^7.25.7" + "@babel/plugin-transform-async-generator-functions": "npm:^7.25.8" "@babel/plugin-transform-async-to-generator": "npm:^7.25.7" "@babel/plugin-transform-block-scoped-functions": "npm:^7.25.7" "@babel/plugin-transform-block-scoping": "npm:^7.25.7" "@babel/plugin-transform-class-properties": "npm:^7.25.7" - "@babel/plugin-transform-class-static-block": "npm:^7.25.7" + "@babel/plugin-transform-class-static-block": "npm:^7.25.8" "@babel/plugin-transform-classes": "npm:^7.25.7" "@babel/plugin-transform-computed-properties": "npm:^7.25.7" "@babel/plugin-transform-destructuring": "npm:^7.25.7" "@babel/plugin-transform-dotall-regex": "npm:^7.25.7" "@babel/plugin-transform-duplicate-keys": "npm:^7.25.7" "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "npm:^7.25.7" - "@babel/plugin-transform-dynamic-import": "npm:^7.25.7" + "@babel/plugin-transform-dynamic-import": "npm:^7.25.8" "@babel/plugin-transform-exponentiation-operator": "npm:^7.25.7" - "@babel/plugin-transform-export-namespace-from": "npm:^7.25.7" + "@babel/plugin-transform-export-namespace-from": "npm:^7.25.8" "@babel/plugin-transform-for-of": "npm:^7.25.7" "@babel/plugin-transform-function-name": "npm:^7.25.7" - "@babel/plugin-transform-json-strings": "npm:^7.25.7" + "@babel/plugin-transform-json-strings": "npm:^7.25.8" "@babel/plugin-transform-literals": "npm:^7.25.7" - "@babel/plugin-transform-logical-assignment-operators": "npm:^7.25.7" + "@babel/plugin-transform-logical-assignment-operators": "npm:^7.25.8" "@babel/plugin-transform-member-expression-literals": "npm:^7.25.7" "@babel/plugin-transform-modules-amd": "npm:^7.25.7" "@babel/plugin-transform-modules-commonjs": "npm:^7.25.7" @@ -1439,15 +1390,15 @@ __metadata: "@babel/plugin-transform-modules-umd": "npm:^7.25.7" "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.25.7" "@babel/plugin-transform-new-target": "npm:^7.25.7" - "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.25.7" - "@babel/plugin-transform-numeric-separator": "npm:^7.25.7" - "@babel/plugin-transform-object-rest-spread": "npm:^7.25.7" + "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.25.8" + "@babel/plugin-transform-numeric-separator": "npm:^7.25.8" + "@babel/plugin-transform-object-rest-spread": "npm:^7.25.8" "@babel/plugin-transform-object-super": "npm:^7.25.7" - "@babel/plugin-transform-optional-catch-binding": "npm:^7.25.7" - "@babel/plugin-transform-optional-chaining": "npm:^7.25.7" + "@babel/plugin-transform-optional-catch-binding": "npm:^7.25.8" + "@babel/plugin-transform-optional-chaining": "npm:^7.25.8" "@babel/plugin-transform-parameters": "npm:^7.25.7" "@babel/plugin-transform-private-methods": "npm:^7.25.7" - "@babel/plugin-transform-private-property-in-object": "npm:^7.25.7" + "@babel/plugin-transform-private-property-in-object": "npm:^7.25.8" "@babel/plugin-transform-property-literals": "npm:^7.25.7" "@babel/plugin-transform-regenerator": "npm:^7.25.7" "@babel/plugin-transform-reserved-words": "npm:^7.25.7" @@ -1468,7 +1419,7 @@ __metadata: semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/2d09aa2cb765a9a1c8a7566e22948e69d69de6b9b763322790bd72ec0bd5bcadf7b02abbc6ad1bab917e1f6d60b1a7f4606778d88cc7d7e1388cc52d292db97d + checksum: 10/501d78f56df8bf6f98a42da5db475db183048c4280b3292cf988b6baf01843915161f3b341ed525e2fcafcc47726798532b0e1dc7eb80aa29cc88c9d6f94ee6e languageName: node linkType: hard @@ -1554,7 +1505,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:7.25.7, @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.14.0, @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.1, @babel/runtime@npm:^7.24.5, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:7.25.7, @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.14.0, @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.1, @babel/runtime@npm:^7.24.5, @babel/runtime@npm:^7.25.6, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": version: 7.25.7 resolution: "@babel/runtime@npm:7.25.7" dependencies: @@ -1589,14 +1540,14 @@ __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.0, @babel/types@npm:^7.24.7, @babel/types@npm:^7.25.7, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": - version: 7.25.7 - resolution: "@babel/types@npm:7.25.7" +"@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.0, @babel/types@npm:^7.24.7, @babel/types@npm:^7.25.7, @babel/types@npm:^7.25.8, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": + version: 7.25.8 + resolution: "@babel/types@npm:7.25.8" dependencies: "@babel/helper-string-parser": "npm:^7.25.7" "@babel/helper-validator-identifier": "npm:^7.25.7" to-fast-properties: "npm:^2.0.0" - checksum: 10/4504e16a95b6a67d50cfaa389bcbc0621019084cff73784ad4797f82d1bb76c870cb0abb6d9881d5776eb06b4607419a2b1205a08c3e87b152d74bd0884b822a + checksum: 10/973108dbb189916bb87360f2beff43ae97f1b08f1c071bc6499d363cce48b3c71674bf3b59dfd617f8c5062d1c76dc2a64232bc07b6ccef831fd0c06162d44d9 languageName: node linkType: hard @@ -3035,13 +2986,14 @@ __metadata: languageName: node linkType: hard -"@formatjs/ecma402-abstract@npm:2.0.0": - version: 2.0.0 - resolution: "@formatjs/ecma402-abstract@npm:2.0.0" +"@formatjs/ecma402-abstract@npm:2.1.0": + version: 2.1.0 + resolution: "@formatjs/ecma402-abstract@npm:2.1.0" dependencies: + "@formatjs/fast-memoize": "npm:2.2.0" "@formatjs/intl-localematcher": "npm:0.5.4" tslib: "npm:^2.4.0" - checksum: 10/41543ba509ea3c7d6530d57b888115f7ca242f13462a951fae4d1d1f28bae10c999f4dea28a71d2f08366d4889a3f5276cae3a16c6f6417b841a84fd314c2234 + checksum: 10/8e25d2739c03913c61f0ec12a71ab7baf03386cff540e7470bee73e78e4fe9f09c9dfa860223f1177acdcfd0fa341041d25b2fe4e0a77e7209d0680831982bac languageName: node linkType: hard @@ -3054,6 +3006,15 @@ __metadata: languageName: node linkType: hard +"@formatjs/fast-memoize@npm:2.2.0": + version: 2.2.0 + resolution: "@formatjs/fast-memoize@npm:2.2.0" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/8697fe72a7ece252d600a7d08105f2a2f758e2dd96f54ac0a4c508b1205a559fc08835635e1f8e5ca9dcc3ee61ce1fca4a0e7047b402f29fc96051e293a280ff + languageName: node + linkType: hard + "@formatjs/icu-messageformat-parser@npm:2.1.7": version: 2.1.7 resolution: "@formatjs/icu-messageformat-parser@npm:2.1.7" @@ -3075,14 +3036,14 @@ __metadata: languageName: node linkType: hard -"@formatjs/intl-durationformat@npm:^0.2.4": - version: 0.2.4 - resolution: "@formatjs/intl-durationformat@npm:0.2.4" +"@formatjs/intl-durationformat@npm:^0.3.0": + version: 0.3.0 + resolution: "@formatjs/intl-durationformat@npm:0.3.0" dependencies: - "@formatjs/ecma402-abstract": "npm:2.0.0" + "@formatjs/ecma402-abstract": "npm:2.1.0" "@formatjs/intl-localematcher": "npm:0.5.4" tslib: "npm:^2.4.0" - checksum: 10/5f500409a20d18967e17ffbc222f9b4c4bf7ef08cce20023c33f06d1989c2bc4cf700d1dd1d048748d0a36c882109d5375896a4964d6700f73ec18914c6de4ba + checksum: 10/96625ba190040e8233a8fd0b8c73347a1e780641868ba429910da046fc971b3c6116b1f93f6020e0eafc1d7ecda6ea1cd25f6048087a10300278fa31495534d3 languageName: node linkType: hard @@ -3135,7 +3096,7 @@ __metadata: "@emotion/css": "npm:11.13.4" "@grafana/data": "npm:11.3.0-pre" "@grafana/e2e-selectors": "npm:11.3.0-pre" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/plugin-configs": "npm:11.3.0-pre" "@grafana/runtime": "npm:11.3.0-pre" "@grafana/schema": "npm:11.3.0-pre" @@ -3179,7 +3140,7 @@ __metadata: "@emotion/css": "npm:11.13.4" "@grafana/data": "npm:11.3.0-pre" "@grafana/e2e-selectors": "npm:11.3.0-pre" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/plugin-configs": "npm:11.3.0-pre" "@grafana/runtime": "npm:11.3.0-pre" "@grafana/sql": "npm:11.3.0-pre" @@ -3251,7 +3212,7 @@ __metadata: "@emotion/css": "npm:11.13.4" "@grafana/data": "npm:11.3.0-pre" "@grafana/e2e-selectors": "npm:11.3.0-pre" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/plugin-configs": "npm:11.3.0-pre" "@grafana/runtime": "npm:11.3.0-pre" "@grafana/schema": "npm:11.3.0-pre" @@ -3292,7 +3253,7 @@ __metadata: "@emotion/css": "npm:11.13.4" "@grafana/data": "workspace:*" "@grafana/e2e-selectors": "workspace:*" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/o11y-ds-frontend": "workspace:*" "@grafana/plugin-configs": "workspace:*" "@grafana/runtime": "workspace:*" @@ -3334,7 +3295,7 @@ __metadata: "@emotion/css": "npm:11.13.4" "@grafana/data": "npm:11.3.0-pre" "@grafana/e2e-selectors": "npm:11.3.0-pre" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/plugin-configs": "npm:11.3.0-pre" "@grafana/runtime": "npm:11.3.0-pre" "@grafana/sql": "npm:11.3.0-pre" @@ -3365,7 +3326,7 @@ __metadata: "@emotion/css": "npm:11.13.4" "@grafana/data": "npm:11.3.0-pre" "@grafana/e2e-selectors": "npm:11.3.0-pre" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/plugin-configs": "npm:11.3.0-pre" "@grafana/runtime": "npm:11.3.0-pre" "@grafana/sql": "npm:11.3.0-pre" @@ -3428,7 +3389,7 @@ __metadata: "@emotion/css": "npm:11.13.4" "@grafana/data": "npm:11.3.0-pre" "@grafana/e2e-selectors": "npm:11.3.0-pre" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/google-sdk": "npm:0.1.2" "@grafana/plugin-configs": "npm:11.3.0-pre" "@grafana/runtime": "npm:11.3.0-pre" @@ -3476,7 +3437,7 @@ __metadata: "@emotion/css": "npm:11.13.4" "@grafana/data": "workspace:*" "@grafana/e2e-selectors": "workspace:*" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/lezer-logql": "npm:0.2.6" "@grafana/lezer-traceql": "npm:0.0.19" "@grafana/monaco-logql": "npm:^0.0.7" @@ -3536,7 +3497,7 @@ __metadata: "@emotion/css": "npm:11.13.4" "@grafana/data": "workspace:*" "@grafana/e2e-selectors": "workspace:*" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/o11y-ds-frontend": "workspace:*" "@grafana/plugin-configs": "workspace:*" "@grafana/runtime": "workspace:*" @@ -3732,6 +3693,32 @@ __metadata: languageName: node linkType: hard +"@grafana/experimental@npm:2.1.2": + version: 2.1.2 + resolution: "@grafana/experimental@npm:2.1.2" + dependencies: + "@hello-pangea/dnd": "npm:^16.6.0" + "@types/uuid": "npm:^8.3.3" + lodash: "npm:^4.17.21" + prismjs: "npm:^1.29.0" + react-popper-tooltip: "npm:^4.4.2" + react-use: "npm:^17.4.2" + semver: "npm:^7.5.4" + uuid: "npm:^8.3.2" + peerDependencies: + "@emotion/css": ^11.11.2 + "@grafana/data": ^10.4.0 || ^11.0.0 + "@grafana/e2e-selectors": ^10.0.0 || ^11.0.0 + "@grafana/runtime": ^10.4.0 || ^11.0.0 + "@grafana/ui": ^10.4.0 || ^11.0.0 + react: ^18.2.0 + react-dom: ^18.2.0 + react-select: ^5.8.0 + rxjs: ^7.8.1 + checksum: 10/6a00737f870a842d1178ab8515de06fb5eb845a5d6f2adcb32e3dcb5bc76af7d2f72c8c5ea5cedbb22526277bdd32916089bad47b9b04a65b95780244ac1558f + languageName: node + linkType: hard + "@grafana/faro-core@npm:^1.10.2, @grafana/faro-core@npm:^1.3.6": version: 1.10.2 resolution: "@grafana/faro-core@npm:1.10.2" @@ -3777,8 +3764,8 @@ __metadata: version: 0.0.0-use.local resolution: "@grafana/flamegraph@workspace:packages/grafana-flamegraph" dependencies: - "@babel/core": "npm:7.25.7" - "@babel/preset-env": "npm:7.25.7" + "@babel/core": "npm:7.25.8" + "@babel/preset-env": "npm:7.25.8" "@babel/preset-react": "npm:7.25.7" "@emotion/css": "npm:11.13.4" "@grafana/data": "npm:11.3.0-pre" @@ -3867,7 +3854,7 @@ __metadata: "@emotion/css": "npm:11.13.4" "@grafana/data": "npm:11.3.0-pre" "@grafana/e2e-selectors": "npm:11.3.0-pre" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/runtime": "npm:11.3.0-pre" "@grafana/schema": "npm:11.3.0-pre" "@grafana/tsconfig": "npm:^2.0.0" @@ -3938,13 +3925,13 @@ __metadata: "@floating-ui/react": "npm:0.26.24" "@grafana/data": "npm:11.3.0-pre" "@grafana/e2e-selectors": "npm:11.3.0-pre" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/faro-web-sdk": "npm:1.10.2" "@grafana/runtime": "npm:11.3.0-pre" "@grafana/schema": "npm:11.3.0-pre" "@grafana/tsconfig": "npm:^2.0.0" "@grafana/ui": "npm:11.3.0-pre" - "@hello-pangea/dnd": "npm:16.6.0" + "@hello-pangea/dnd": "npm:17.0.0" "@leeoniya/ufuzzy": "npm:1.0.14" "@lezer/common": "npm:1.2.2" "@lezer/highlight": "npm:1.2.1" @@ -4022,7 +4009,7 @@ __metadata: rollup-plugin-esbuild: "npm:6.1.1" rollup-plugin-node-externals: "npm:^7.1.3" rxjs: "npm:7.8.1" - sass: "npm:1.79.3" + sass: "npm:1.79.4" sass-loader: "npm:16.0.2" semver: "npm:7.6.3" style-loader: "npm:4.0.0" @@ -4086,7 +4073,7 @@ __metadata: version: 0.0.0-use.local resolution: "@grafana/saga-icons@workspace:packages/grafana-icons" dependencies: - "@babel/core": "npm:7.25.7" + "@babel/core": "npm:7.25.8" "@grafana/tsconfig": "npm:^2.0.0" "@rollup/plugin-node-resolve": "npm:^15.3.0" "@rollup/plugin-typescript": "npm:^12.1.0" @@ -4117,9 +4104,27 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes@npm:5.18.3": - version: 5.18.3 - resolution: "@grafana/scenes@npm:5.18.3" +"@grafana/scenes-react@npm:5.20.0": + version: 5.20.0 + resolution: "@grafana/scenes-react@npm:5.20.0" + dependencies: + "@grafana/e2e-selectors": "npm:^11.0.0" + "@grafana/scenes": "npm:5.20.0" + react-use: "npm:17.4.0" + peerDependencies: + "@grafana/data": ^11.0.0 + "@grafana/runtime": ^11.0.0 + "@grafana/schema": ^11.0.0 + "@grafana/ui": ^11.0.0 + react: ^18.0.0 + react-dom: ^18.0.0 + checksum: 10/9e4b4f678ad4e072876eaa018e6c67e2fd19d125202b95f8a2f18f113d25693a4a2387ada17cd032f5f11ed2fd189ef471148a25e74f55cc620f22c5e6016a48 + languageName: node + linkType: hard + +"@grafana/scenes@npm:5.20.0": + version: 5.20.0 + resolution: "@grafana/scenes@npm:5.20.0" dependencies: "@floating-ui/react": "npm:0.26.16" "@grafana/e2e-selectors": "npm:^11.0.0" @@ -4136,7 +4141,7 @@ __metadata: "@grafana/ui": ">=10.4" react: ^18.0.0 react-dom: ^18.0.0 - checksum: 10/fb1179db5b53d709b859facc7742473dbd13947c2ec9593035e617f32166716de20b712002c14e1eec7cf90a80e58c60cdc039a5252a54c7ab1f8659bba64476 + checksum: 10/0bc36324f5c109175199b5e9523323397acd6b95c0be11856f54394bb2a51eb4e0720ecabd37acac9966a7de69f9ddcf330c1ed03e0c196b76e7188f65de88c1 languageName: node linkType: hard @@ -4165,7 +4170,7 @@ __metadata: "@emotion/css": "npm:11.13.4" "@grafana/data": "npm:11.3.0-pre" "@grafana/e2e-selectors": "npm:11.3.0-pre" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/runtime": "npm:11.3.0-pre" "@grafana/tsconfig": "npm:^2.0.0" "@grafana/ui": "npm:11.3.0-pre" @@ -4222,7 +4227,7 @@ __metadata: version: 0.0.0-use.local resolution: "@grafana/ui@workspace:packages/grafana-ui" dependencies: - "@babel/core": "npm:7.25.7" + "@babel/core": "npm:7.25.8" "@emotion/css": "npm:11.13.4" "@emotion/react": "npm:11.13.3" "@emotion/serialize": "npm:1.3.2" @@ -4233,7 +4238,7 @@ __metadata: "@grafana/faro-web-sdk": "npm:^1.3.6" "@grafana/schema": "npm:11.3.0-pre" "@grafana/tsconfig": "npm:^2.0.0" - "@hello-pangea/dnd": "npm:16.6.0" + "@hello-pangea/dnd": "npm:17.0.0" "@leeoniya/ufuzzy": "npm:1.0.14" "@monaco-editor/react": "npm:4.6.0" "@popperjs/core": "npm:2.11.8" @@ -4375,7 +4380,25 @@ __metadata: languageName: node linkType: hard -"@hello-pangea/dnd@npm:16.6.0, @hello-pangea/dnd@npm:^16.6.0": +"@hello-pangea/dnd@npm:17.0.0": + version: 17.0.0 + resolution: "@hello-pangea/dnd@npm:17.0.0" + dependencies: + "@babel/runtime": "npm:^7.25.6" + css-box-model: "npm:^1.2.1" + memoize-one: "npm:^6.0.0" + raf-schd: "npm:^4.0.3" + react-redux: "npm:^9.1.2" + redux: "npm:^5.0.1" + use-memo-one: "npm:^1.1.3" + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + checksum: 10/4795063e249a818c60e223f3527797878cb546ef007a52a7dd6c1a01094d3b2107820476a10fc83c0ba9dc4387c1ae49e70c8f8cff9722636219773caad19372 + languageName: node + linkType: hard + +"@hello-pangea/dnd@npm:^16.6.0": version: 16.6.0 resolution: "@hello-pangea/dnd@npm:16.6.0" dependencies: @@ -6837,18 +6860,7 @@ __metadata: languageName: node linkType: hard -"@react-types/button@npm:3.9.6": - version: 3.9.6 - resolution: "@react-types/button@npm:3.9.6" - dependencies: - "@react-types/shared": "npm:^3.24.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 - checksum: 10/348096091b39b9cfeaf3b11b4ff262652954ea1793008aa2acc005ca32f299db550f08fe076498501547c2a9a06c46d2000f202fc0dbe853a1202d6523b71449 - languageName: node - linkType: hard - -"@react-types/button@npm:^3.10.0": +"@react-types/button@npm:3.10.0, @react-types/button@npm:^3.10.0": version: 3.10.0 resolution: "@react-types/button@npm:3.10.0" dependencies: @@ -6894,16 +6906,7 @@ __metadata: languageName: node linkType: hard -"@react-types/shared@npm:3.24.1": - version: 3.24.1 - resolution: "@react-types/shared@npm:3.24.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 - checksum: 10/5472ae35f65b2ed7c12d5ea4459f34b4aec065d2633844031d27945495b6dca6fa9bf02b6392b901fac97252e58d9b91a4baf53f4c281397fb81ce85c73b8648 - languageName: node - linkType: hard - -"@react-types/shared@npm:^3.24.1, @react-types/shared@npm:^3.25.0": +"@react-types/shared@npm:3.25.0, @react-types/shared@npm:^3.25.0": version: 3.25.0 resolution: "@react-types/shared@npm:3.25.0" peerDependencies: @@ -7036,114 +7039,114 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.22.5" +"@rollup/rollup-android-arm-eabi@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.24.0" conditions: os=android & cpu=arm languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-android-arm64@npm:4.22.5" +"@rollup/rollup-android-arm64@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-android-arm64@npm:4.24.0" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-darwin-arm64@npm:4.22.5" +"@rollup/rollup-darwin-arm64@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-darwin-arm64@npm:4.24.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-darwin-x64@npm:4.22.5" +"@rollup/rollup-darwin-x64@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-darwin-x64@npm:4.24.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.22.5" +"@rollup/rollup-linux-arm-gnueabihf@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.24.0" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.22.5" +"@rollup/rollup-linux-arm-musleabihf@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.24.0" conditions: os=linux & cpu=arm & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.22.5" +"@rollup/rollup-linux-arm64-gnu@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.24.0" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.22.5" +"@rollup/rollup-linux-arm64-musl@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.24.0" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-powerpc64le-gnu@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.22.5" +"@rollup/rollup-linux-powerpc64le-gnu@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.24.0" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.22.5" +"@rollup/rollup-linux-riscv64-gnu@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.24.0" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.22.5" +"@rollup/rollup-linux-s390x-gnu@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.24.0" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.22.5" +"@rollup/rollup-linux-x64-gnu@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.24.0" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.22.5" +"@rollup/rollup-linux-x64-musl@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.24.0" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.22.5" +"@rollup/rollup-win32-arm64-msvc@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.24.0" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.22.5" +"@rollup/rollup-win32-ia32-msvc@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.24.0" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.22.5" +"@rollup/rollup-win32-x64-msvc@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.24.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -15358,15 +15361,15 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6": - version: 4.3.6 - resolution: "debug@npm:4.3.6" +"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.3.7": + version: 4.3.7 + resolution: "debug@npm:4.3.7" dependencies: - ms: "npm:2.1.2" + ms: "npm:^2.1.3" peerDependenciesMeta: supports-color: optional: true - checksum: 10/d3adb9af7d57a9e809a68f404490cf776122acca16e6359a2702c0f462e510e91f9765c07f707b8ab0d91e03bad57328f3256f5082631cefb5393d0394d50fb7 + checksum: 10/71168908b9a78227ab29d5d25fe03c5867750e31ce24bf2c44a86efc5af041758bb56569b0a3d48a9b5344c00a24a777e6f4100ed6dfd9534a42c1dde285125a languageName: node linkType: hard @@ -16484,16 +16487,16 @@ __metadata: languageName: node linkType: hard -"esbuild-plugin-browserslist@npm:^0.14.0": - version: 0.14.0 - resolution: "esbuild-plugin-browserslist@npm:0.14.0" +"esbuild-plugin-browserslist@npm:^0.15.0": + version: 0.15.0 + resolution: "esbuild-plugin-browserslist@npm:0.15.0" dependencies: - debug: "npm:^4.3.5" + debug: "npm:^4.3.7" zod: "npm:^3.23.8" peerDependencies: browserslist: ^4.21.8 - esbuild: ~0.23.0 - checksum: 10/634d8a562597dd3c21cad10785153968d7b80f97ee8d11bac9611e1aa524291084e1c5965213442f1a9d07a98fc8862bd00921aa7cb130716f67d2ac8c2a6cc2 + esbuild: ~0.24.0 + checksum: 10/b1afe26f5c013a37664ff6ca2f8190d50e36f719afca8aba2c0b18f0fd0f34de3334db1435154a70dbfeb5e91acf732cc20296bb3fd3cbdfe68ae37dadcffe32 languageName: node linkType: hard @@ -16927,21 +16930,21 @@ __metadata: languageName: node linkType: hard -"eslint-module-utils@npm:^2.9.0": - version: 2.9.0 - resolution: "eslint-module-utils@npm:2.9.0" +"eslint-module-utils@npm:^2.12.0": + version: 2.12.0 + resolution: "eslint-module-utils@npm:2.12.0" dependencies: debug: "npm:^3.2.7" peerDependenciesMeta: eslint: optional: true - checksum: 10/13e001c96a6ce8d3d7ad6798c9b86351820c9c4a9abc5a152e84b838d7937a781471b0128ee690d18def226741fc96e8c5cff78c059bdcafe9ab8625777fcf2a + checksum: 10/dd27791147eca17366afcb83f47d6825b6ce164abb256681e5de4ec1d7e87d8605641eb869298a0dbc70665e2446dbcc2f40d3e1631a9475dd64dd23d4ca5dee languageName: node linkType: hard "eslint-plugin-import@npm:^2.26.0": - version: 2.30.0 - resolution: "eslint-plugin-import@npm:2.30.0" + version: 2.31.0 + resolution: "eslint-plugin-import@npm:2.31.0" dependencies: "@rtsao/scc": "npm:^1.1.0" array-includes: "npm:^3.1.8" @@ -16951,7 +16954,7 @@ __metadata: debug: "npm:^3.2.7" doctrine: "npm:^2.1.0" eslint-import-resolver-node: "npm:^0.3.9" - eslint-module-utils: "npm:^2.9.0" + eslint-module-utils: "npm:^2.12.0" hasown: "npm:^2.0.2" is-core-module: "npm:^2.15.1" is-glob: "npm:^4.0.3" @@ -16960,10 +16963,11 @@ __metadata: object.groupby: "npm:^1.0.3" object.values: "npm:^1.2.0" semver: "npm:^6.3.1" + string.prototype.trimend: "npm:^1.0.8" tsconfig-paths: "npm:^3.15.0" peerDependencies: - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - checksum: 10/a5f85dfe76e27286c28a01d137769726ce3f758bcc03aa6b6f9e18700a40a08f57239f82e07efcab763c4b03a02d425edcc29fbecf40aad0124286978c6bc63c + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + checksum: 10/6b76bd009ac2db0615d9019699d18e2a51a86cb8c1d0855a35fb1b418be23b40239e6debdc6e8c92c59f1468ed0ea8d7b85c817117a113d5cc225be8a02ad31c languageName: node linkType: hard @@ -17182,12 +17186,12 @@ __metadata: linkType: hard "eslint-scope@npm:^8.0.0": - version: 8.0.2 - resolution: "eslint-scope@npm:8.0.2" + version: 8.1.0 + resolution: "eslint-scope@npm:8.1.0" dependencies: esrecurse: "npm:^4.3.0" estraverse: "npm:^5.2.0" - checksum: 10/d17c2e1ff4d3a98911414a954531078db912e2747d6da8ea4cafd16d0526e32086c676ce9aeaffb3ca0ff695fc951ac3169d7f08a0b42962db683dff126cc95b + checksum: 10/4c34a12fbeb0677822a9e93e81f2027e39e6f27557c17bc1e5ff76debbd41e748c3673517561792bda9e276245f89fbfd9b0b24fcec3b33a04ee2196729b3489 languageName: node linkType: hard @@ -18889,8 +18893,8 @@ __metadata: version: 0.0.0-use.local resolution: "grafana@workspace:." dependencies: - "@babel/core": "npm:7.25.7" - "@babel/preset-env": "npm:7.25.7" + "@babel/core": "npm:7.25.8" + "@babel/preset-env": "npm:7.25.8" "@babel/runtime": "npm:7.25.7" "@betterer/betterer": "npm:5.4.0" "@betterer/cli": "npm:5.4.0" @@ -18901,7 +18905,7 @@ __metadata: "@emotion/react": "npm:11.13.3" "@fingerprintjs/fingerprintjs": "npm:^3.4.2" "@floating-ui/react": "npm:0.26.24" - "@formatjs/intl-durationformat": "npm:^0.2.4" + "@formatjs/intl-durationformat": "npm:^0.3.0" "@glideapps/glide-data-grid": "npm:^6.0.0" "@grafana/aws-sdk": "npm:0.5.0" "@grafana/azure-sdk": "npm:0.0.3" @@ -18909,7 +18913,7 @@ __metadata: "@grafana/e2e-selectors": "workspace:*" "@grafana/eslint-config": "npm:7.0.0" "@grafana/eslint-plugin": "link:./packages/grafana-eslint-rules" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/faro-core": "npm:^1.3.6" "@grafana/faro-web-sdk": "npm:^1.3.6" "@grafana/faro-web-tracing": "npm:^1.8.2" @@ -18922,12 +18926,13 @@ __metadata: "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" "@grafana/saga-icons": "workspace:*" - "@grafana/scenes": "npm:5.18.3" + "@grafana/scenes": "npm:5.20.0" + "@grafana/scenes-react": "npm:5.20.0" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/tsconfig": "npm:^2.0.0" "@grafana/ui": "workspace:*" - "@hello-pangea/dnd": "npm:16.6.0" + "@hello-pangea/dnd": "npm:17.0.0" "@kusto/monaco-kusto": "npm:^10.0.0" "@leeoniya/ufuzzy": "npm:1.0.14" "@lezer/common": "npm:1.2.2" @@ -18950,10 +18955,10 @@ __metadata: "@react-aria/overlays": "npm:3.23.3" "@react-aria/utils": "npm:3.25.3" "@react-awesome-query-builder/ui": "npm:6.6.3" - "@react-types/button": "npm:3.9.6" + "@react-types/button": "npm:3.10.0" "@react-types/menu": "npm:3.9.12" "@react-types/overlays": "npm:3.8.10" - "@react-types/shared": "npm:3.24.1" + "@react-types/shared": "npm:3.25.0" "@reduxjs/toolkit": "npm:2.2.8" "@rtk-query/codegen-openapi": "npm:^1.2.0" "@rtsao/plugin-proposal-class-properties": "npm:7.0.1-patch.1" @@ -19064,7 +19069,7 @@ __metadata: diff: "npm:^5.1.0" esbuild: "npm:0.24.0" esbuild-loader: "npm:4.2.2" - esbuild-plugin-browserslist: "npm:^0.14.0" + esbuild-plugin-browserslist: "npm:^0.15.0" eslint: "npm:8.57.0" eslint-config-prettier: "npm:9.1.0" eslint-plugin-import: "npm:^2.26.0" @@ -19188,7 +19193,7 @@ __metadata: rimraf: "npm:6.0.1" rudder-sdk-js: "npm:2.48.19" rxjs: "npm:7.8.1" - sass: "npm:1.79.3" + sass: "npm:1.79.4" sass-loader: "npm:16.0.2" selecto: "npm:1.26.3" semver: "npm:7.6.3" @@ -21639,7 +21644,7 @@ __metadata: languageName: node linkType: hard -"jiti@npm:^1.20.0, jiti@npm:^1.21.6": +"jiti@npm:^1.20.0": version: 1.21.6 resolution: "jiti@npm:1.21.6" bin: @@ -21648,6 +21653,15 @@ __metadata: languageName: node linkType: hard +"jiti@npm:^2.3.3": + version: 2.3.3 + resolution: "jiti@npm:2.3.3" + bin: + jiti: lib/jiti-cli.mjs + checksum: 10/21d1e89d909101c702769537e75ad87b433f980bc6938a6f64a90d1fbe7cb1510a0d4b82d4020b3093b47cebff5568af5e39d883e26aa4413564ced43b8cfd84 + languageName: node + linkType: hard + "jju@npm:^1.4.0": version: 1.4.0 resolution: "jju@npm:1.4.0" @@ -22111,15 +22125,15 @@ __metadata: linkType: hard "knip@npm:^5.10.0": - version: 5.30.6 - resolution: "knip@npm:5.30.6" + version: 5.33.3 + resolution: "knip@npm:5.33.3" dependencies: "@nodelib/fs.walk": "npm:1.2.8" "@snyk/github-codeowners": "npm:1.1.0" easy-table: "npm:1.2.0" enhanced-resolve: "npm:^5.17.1" fast-glob: "npm:^3.3.2" - jiti: "npm:^1.21.6" + jiti: "npm:^2.3.3" js-yaml: "npm:^4.1.0" minimist: "npm:^1.2.8" picocolors: "npm:^1.0.0" @@ -22136,7 +22150,7 @@ __metadata: bin: knip: bin/knip.js knip-bun: bin/knip-bun.js - checksum: 10/42973ec1f5208017c63232dd5a8c29b32accc9bbc00712afbb247fe14b2e8687e27a66a91cf48250cdae30aff6c456d71271c82dda9136785f96520b8c8a049d + checksum: 10/d6227a43666ce9732fe6b797a0c348bc46f59603826766c2049f44682ce96c23f0fb85b659d243dd36f98098a83a4e500d1044f3114a32564a39cf92dce8e853 languageName: node linkType: hard @@ -24148,14 +24162,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:2.1.2": - version: 2.1.2 - resolution: "ms@npm:2.1.2" - checksum: 10/673cdb2c3133eb050c745908d8ce632ed2c02d85640e2edb3ace856a2266a813b30c613569bf3354fdf4ea7d1a1494add3bfa95e2713baa27d0c2c71fc44f58f - languageName: node - linkType: hard - -"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1": +"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: 10/aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d @@ -24249,7 +24256,7 @@ __metadata: languageName: node linkType: hard -"nano-css@npm:^5.6.1, nano-css@npm:^5.6.2": +"nano-css@npm:^5.3.1, nano-css@npm:^5.6.1, nano-css@npm:^5.6.2": version: 5.6.2 resolution: "nano-css@npm:5.6.2" dependencies: @@ -28003,6 +28010,31 @@ __metadata: languageName: node linkType: hard +"react-use@npm:17.4.0": + version: 17.4.0 + resolution: "react-use@npm:17.4.0" + dependencies: + "@types/js-cookie": "npm:^2.2.6" + "@xobotyi/scrollbar-width": "npm:^1.9.5" + copy-to-clipboard: "npm:^3.3.1" + fast-deep-equal: "npm:^3.1.3" + fast-shallow-equal: "npm:^1.0.0" + js-cookie: "npm:^2.2.1" + nano-css: "npm:^5.3.1" + react-universal-interface: "npm:^0.6.2" + resize-observer-polyfill: "npm:^1.5.1" + screenfull: "npm:^5.1.0" + set-harmonic-interval: "npm:^1.0.1" + throttle-debounce: "npm:^3.0.1" + ts-easing: "npm:^0.2.0" + tslib: "npm:^2.1.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 10/98566c4817b00251107824743ea9dff41f167b548bd5f249f6eb9e2ec09388a2de1e89988e4432cead3f8aa83cf706e0255db8a20c0615768c670751973d2761 + languageName: node + linkType: hard + "react-use@npm:17.5.0": version: 17.5.0 resolution: "react-use@npm:17.5.0" @@ -28947,25 +28979,25 @@ __metadata: linkType: hard "rollup@npm:^4.22.4": - version: 4.22.5 - resolution: "rollup@npm:4.22.5" + version: 4.24.0 + resolution: "rollup@npm:4.24.0" dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.22.5" - "@rollup/rollup-android-arm64": "npm:4.22.5" - "@rollup/rollup-darwin-arm64": "npm:4.22.5" - "@rollup/rollup-darwin-x64": "npm:4.22.5" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.22.5" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.22.5" - "@rollup/rollup-linux-arm64-gnu": "npm:4.22.5" - "@rollup/rollup-linux-arm64-musl": "npm:4.22.5" - "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.22.5" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.22.5" - "@rollup/rollup-linux-s390x-gnu": "npm:4.22.5" - "@rollup/rollup-linux-x64-gnu": "npm:4.22.5" - "@rollup/rollup-linux-x64-musl": "npm:4.22.5" - "@rollup/rollup-win32-arm64-msvc": "npm:4.22.5" - "@rollup/rollup-win32-ia32-msvc": "npm:4.22.5" - "@rollup/rollup-win32-x64-msvc": "npm:4.22.5" + "@rollup/rollup-android-arm-eabi": "npm:4.24.0" + "@rollup/rollup-android-arm64": "npm:4.24.0" + "@rollup/rollup-darwin-arm64": "npm:4.24.0" + "@rollup/rollup-darwin-x64": "npm:4.24.0" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.24.0" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.24.0" + "@rollup/rollup-linux-arm64-gnu": "npm:4.24.0" + "@rollup/rollup-linux-arm64-musl": "npm:4.24.0" + "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.24.0" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.24.0" + "@rollup/rollup-linux-s390x-gnu": "npm:4.24.0" + "@rollup/rollup-linux-x64-gnu": "npm:4.24.0" + "@rollup/rollup-linux-x64-musl": "npm:4.24.0" + "@rollup/rollup-win32-arm64-msvc": "npm:4.24.0" + "@rollup/rollup-win32-ia32-msvc": "npm:4.24.0" + "@rollup/rollup-win32-x64-msvc": "npm:4.24.0" "@types/estree": "npm:1.0.6" fsevents: "npm:~2.3.2" dependenciesMeta: @@ -29005,7 +29037,7 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: 10/f34812fa982442ab71410b649630c24434b2dc02485e543607734766eb7211ce7e0a79102f27210f337af00f3617006adebb4f87fb2e9d24cac7100d0e599352 + checksum: 10/291dce8f180628a73d6749119a3e50aa917c416075302bc6f6ac655affc7f0ce9d7f025bef7318d424d0c5623dcb83e360f9ea0125273b6a2285c232172800cc languageName: node linkType: hard @@ -29155,16 +29187,16 @@ __metadata: languageName: node linkType: hard -"sass@npm:1.79.3": - version: 1.79.3 - resolution: "sass@npm:1.79.3" +"sass@npm:1.79.4": + version: 1.79.4 + resolution: "sass@npm:1.79.4" dependencies: chokidar: "npm:^4.0.0" immutable: "npm:^4.0.0" source-map-js: "npm:>=0.6.2 <2.0.0" bin: sass: sass.js - checksum: 10/9b83e91c44a4c5d738ded27fcd2c88260f5f407e49c6aab92f75a1f768831182a8f4f89f30e2599b74447451d6536ea6f3a55cac34c5ec00b45983d493732e5d + checksum: 10/82e2ee5c2e46c96818454c7d97bcfb5b36c1c27de3b1e705adad7a49a8b32226c5254cc4c8804f45db2b6aa018848973177274c2b1137d4caf7abb5cb7bbf8b9 languageName: node linkType: hard